├── .github └── workflows │ └── test-itunes.yml ├── action.yml └── workflow_helper ├── iTunesDownload ├── get_header.py └── get_header_rpc.js ├── iTunesInstall ├── install_itunes.bat └── patch_itunes.py └── itunes_auto_login.py /.github/workflows/test-itunes.yml: -------------------------------------------------------------------------------- 1 | # vim: expandtab tabstop=2 shiftwidth=2 2 | name: Test iTunes Header Server 3 | 4 | env: 5 | PYTHONIOENCODING: utf-8 6 | 7 | on: 8 | push: 9 | workflow_dispatch: 10 | inputs: 11 | itunes_debug_enabled: 12 | description: 'Run the build with iTunes ngrok-RDP debugging enabled' 13 | required: false 14 | default: false 15 | test_debug_enabled: 16 | description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' 17 | required: false 18 | default: false 19 | repository_dispatch: 20 | 21 | jobs: 22 | header_server: 23 | name: 'Test Setup' 24 | runs-on: "windows-latest" 25 | steps: 26 | - name: Set up git repository 27 | uses: actions/checkout@v2 28 | - name: Setup iTunes 29 | uses: NyaMisty/actions-iTunes-header@master 30 | with: 31 | apple_id: ${{ secrets.APPLEID }} 32 | apple_id_pwd: ${{ secrets.APPLEID_PWD }} 33 | ngrok_token: ${{ secrets.NGROK_AUTH_TOKEN }} 34 | 35 | # Enable tmate debugging of manually-triggered workflows if the input option was provided 36 | # - name: Setup tmate session 37 | # uses: mxschmitt/action-tmate@v3 38 | # if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.test_debug_enabled }} 39 | # env: 40 | # SECRETS_CONTEXT: ${{ toJson(secrets) }} 41 | 42 | - name: Test iTunes Server 43 | run: | 44 | curl -vv 127.0.0.1:9000 45 | 46 | #- uses: NyaMisty/reverse-rdp-windows-github-actions-ng@master 47 | # if: ${{ always() && github.event_name == 'workflow_dispatch' && github.event.inputs.test_debug_enabled }} 48 | # with: 49 | # ngrok-token: ${{ secrets.NGROK_AUTH_TOKEN }} 50 | # password: Aa123456 51 | # foreground: false -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Get iTunes Headers & Kbsync' 2 | inputs: 3 | apple_id: 4 | required: true 5 | apple_id_pwd: 6 | required: true 7 | ngrok_token: 8 | description: "Token for RDP debugging" 9 | required: false 10 | 11 | runs: 12 | using: "composite" 13 | steps: 14 | - name: Check if initialized 15 | id: check 16 | run: | 17 | echo "Checking if we need to initialize iTunes again" 18 | if [[ "$(cat /c/.iTunesInitialized)" == "${{ inputs.ngrok-token }}" ]]; then 19 | echo " Need to re-init iTunes" 20 | echo "NEED_INIT=1" >> $GITHUB_ENV 21 | else 22 | echo " Needn't to do anything" 23 | echo "NEED_INIT=0" >> $GITHUB_ENV 24 | fi 25 | working-directory: ${{ github.action_path }} 26 | shell: bash 27 | - name: Setup iTunes 28 | if: ${{ env.NEED_INIT == 1 }} 29 | run: | 30 | echo Setup iTunes... 31 | start /wait taskkill /f /im iTunes* python* 32 | workflow_helper\iTunesInstall\install_itunes.bat 33 | working-directory: ${{ github.action_path }} 34 | shell: cmd 35 | 36 | - name: Setup Python Dependencies 37 | if: ${{ env.NEED_INIT == 1 }} 38 | run: | 39 | echo Setup Python Dependencies... 40 | pip3 install pywinauto frida Flask 41 | working-directory: ${{ github.action_path }} 42 | shell: cmd 43 | 44 | #- uses: NyaMisty/reverse-rdp-windows-github-actions-ng@master 45 | # if: ${{ always() && github.event_name == 'workflow_dispatch' && github.event.inputs.itunes_debug_enabled }} 46 | # with: 47 | # ngrok-token: ${{ inputs.ngrok_token }} 48 | # password: Aa123456 49 | # foreground: false 50 | 51 | - name: Login iTunes 52 | if: ${{ env.NEED_INIT == 1 }} 53 | env: 54 | APPLEID: ${{ inputs.apple_id }} 55 | APPLEID_PWD: ${{ inputs.apple_id_pwd }} 56 | run: | 57 | echo Login iTunes... 58 | python3 workflow_helper/itunes_auto_login.py %APPLEID% %APPLEID_PWD% 59 | working-directory: ${{ github.action_path }} 60 | shell: cmd 61 | 62 | - name: Finish Initialization 63 | if: ${{ env.NEED_INIT == 1 }} 64 | env: 65 | APPLEID: ${{ inputs.apple_id }} 66 | run: | 67 | echo "Finish Initialization..." 68 | echo "$APPLEID" > /c/.iTunesInitialized 69 | working-directory: ${{ github.action_path }} 70 | shell: bash 71 | 72 | - name: Start Frida Header Server 73 | run: | 74 | echo Start Frida Header Server! 75 | curl -Lo psexec64.exe https://github.com/ComputerGuyYash/psexec/raw/main/PsExec64.exe 76 | psexec64.exe -accepteula -nobanner -i -d cmd /c "python3.exe workflow_helper/iTunesDownload/get_header.py > C:\get_header.log 2>&1" 77 | exit 0 78 | working-directory: ${{ github.action_path }} 79 | shell: cmd 80 | 81 | - name: Test Frida Header Server 82 | run: | 83 | sleep 5 84 | ret=0 85 | echo "---------------- Before query headers ----------------" 86 | cat /c/get_header.log 87 | curl --fail-with-body -vv 127.0.0.1:9000 || ret=$? 88 | echo "---------------- After query headers ----------------" 89 | cat /c/get_header.log 90 | exit $ret 91 | working-directory: ${{ github.action_path }} 92 | shell: bash 93 | -------------------------------------------------------------------------------- /workflow_helper/iTunesDownload/get_header.py: -------------------------------------------------------------------------------- 1 | # frida_rpc_player.py 2 | 3 | ''' 4 | POST /WebObjects/MZBuy.woa/wa/buyProduct HTTP/1.1 5 | Host: p46-buy.itunes.apple.com 6 | User-Agent: iTunes/12.6.5 (Windows; Microsoft Windows 10.0 x64 Enterprise Edition (Build 19042); x64) AppleWebKit/7605.1033.1002.2 7 | Content-Length: 1226 8 | Content-Type: application/x-apple-plist 9 | X-Dsid: 12263680861 10 | X-Apple-Tz: 28800 11 | X-Apple-MD: AAAABAAAABA3hIN/KYf4Ri4tdZAiwaB5 12 | Cookie: amp=JG7EpvImu+/h0yS5mTqQMLarQce6xuYNfyzC6cun/bRt+7VLQUqfUrDB4cdRyV6SLDGBRCG3Oz/PcY28nx94GdTGkdOYX83a54KHZTSq3jI=; amia-12263680861=dPQK5oK/9b6q7HeGIids4a6gE5oiE8yLcVjQO7EknSTERhI0OawL4FQcmJ60560H+PbZhCCt6Na8jLSDiU09aw==; mt-asn-12263680861=2; mt-tkn-12263680861=AmDauzfLChTup9vGq17bGVJvSurhbsyoCLyrpJG8t8uECuyAI3xr3o/vS9pVpKeg4dSlCxJtLUS8+H47Qtpou0VGgGBAn/dXAPozhdJKTyPdUqyT4My89dujXg48XNgIVUjFj2w4IhN1gg5wn3BfSg1bFChodxuHadsRibnYzkUPmR+TVTnivu9e4/QLJmh58Va8rmg=; mt-tkn-10255130069=Ala2nKdNzZWOlapGgvRzcNExINc8j4sbrcMn3ruJ9M3Cb17MGiGqvyTe9yUzh/fSNLEL+4s0j57Eht/AWXrG2e3+GptHZlTi1vZ4a+3PdD9dtfSP8no7s9/bMX6JTtnE+fwV1z8QudsvAs+kjEz0MqFgoxFZZntBKI1L81+CBTnmsBolRdk+wqqPGZ3AD5bdybUTwD4=; countryVerified=1; mzf_in=467080; itspod=46; vrep=CN3W49ctEgQIChAAEgQICBAAEgQIBxAAEgQIBBAAEgQIDRAAEgQIBhAAEgQIDBAAEgQIAxAAEgQICRAAEgQIEBAAEgQIDhAAEgQIARAAEgQIDxAAEgQIERAAEgQIBRAAEgQICxAAEgQIAhAA; wosid-lite=jRoFTHWbZZZkSOfvfDELf0; mz_at_ssl-12263680861=AwUAAAECAAHXHQAAAABgqAeMT5/7LbF5106/zxFn9xH4n7jgwEw=; mz_at0-12263680861=AwQAAAECAAHXHQAAAABgp/7Z1enk1z4615lhp9QKlDQfmo7Hq60=; pldfltcid=524b36a58d0d4249a127affd05921dab046; X-Dsid=12263680861; xp_ab=1#WqjkRLH+-2+TCEF_ea00#yNFpB6B+-2+c9imSgD01; xp_abc=TCEF_ea00; mz_at0-10255130069=AwQAAAECAAHXHQAAAABgnNBzeacT12/WPi/iSOROyaMVQrfmkqE=; mz_at_ssl-10255130069=AwUAAAECAAHXHQAAAABgnNBzNHT9PbcrsAARLEWi0+8ElnKZ54c=; xp_ci=3z3h6Y0vzFQQz4XtzB5DzZvQkYhyo 13 | X-Apple-AMD-M: rjK8HkwTcIoiOk6UoA9LXta0h/19+ss41RU7YLxGunAtLnl1Fus5i4VS56O71ifgDWwrEl/jIbPrDLfS 14 | X-Apple-AMD: AAAABAAAABCqItI0AgfIcPIc0+zGpNtr 15 | X-Apple-Store-Front: 143462-9,32 16 | Accept-Language: zh-cn 17 | Date: Fri, 21 May 2021 21:33:42 GMT 18 | X-Apple-MD-M: rjK8HkwTcIoiOk6UoA9LXta0h/2Qyn969daQuVAdJbkmCnWee7s+joaQ3fvc4v1JTJB0sUJcDwc0Q12z 19 | X-Apple-I-MD-RINFO: 50660608 20 | X-Apple-I-MD-M: rjK8HkwTcIoiOk6UoA9LXta0h/0W22FXNYql4KAM12/n20O3iVf54IK85PbrLb51CvEfn5/b6eoX5d1d 21 | X-Apple-I-MD: AAAABQAAABA+EMexoFPqOMeyQ5zIkQYPAAAAAg== 22 | Accept-Encoding: gzip, deflate 23 | Connection: close 24 | 25 | 26 | 27 | 28 | guid 29 | 46994789.C2E39C5C.B1D62DC2.025D2569.3D6E49B5.AB7F7EB4.F6D17565 30 | kbsync 31 | 32 | AAQAA5+HiDofKn/iW9aKVsKo1XU0cRuzPrQGMfC57fQaA7YaZBWlSS+s4SS2Ay6pchYG 33 | x/h+cJccHMoA/NDtHcWsRl9nmDwzAnuW9WH7nHQAEt95wkj2eMQVBHyj3OSkSoEGFnS8 34 | r4irSIve3V4np1lvbEUgHdJmOEV6gAd8SFzItmgxFImn55qoUX/xt4pdXagPmhBXaYFI 35 | +B/uYLxOYLO4fM1fGpQQ8/1EIpzrbahFjpG8L8kMjhVIWpn2Ut30p2deyjcL47JbSjx7 36 | /RId4XZlSEqXf9SOETcsxCkVhDWTghGYRWKlEe7j32CVrxOkg2lvlQLHB1XBhTpQiLSm 37 | PR0hDIG5dQif0jaIeQJ9zGqq3kgkl9RlFHGbcvfMdrJsYGOgYMzXh1jfF/v21xPfauYh 38 | PIE9LQu/TTiAGDvS+BYrM2xVaPHZPNUpLaVlk/2zIqiWo/Zj2se4FXGOl4LAVKqrbOYN 39 | 2YONkSY4CQOZTmNmoO6SBU2NJHBYtVVeIhOLGLoS9/xzPwMdFevvZJFD7iQvL0RDKCBt 40 | z5r7dnFz2WlZsdx0aMsbV3HRdnJtmBh7zA3AaO/4rJtWAQqAID2gAF8YMoNdJJp13Bdr 41 | f34iRpVYbsDxrBNJXtanTgDdNO3xXpyZhQTgOrBDcsv7mD68E1E6bE5ac97/z9R8cLf0 42 | 8/X8/ie7tUBcqVThW81DiM4RGh3LSg== 43 | 44 | price 45 | 0 46 | pricingParameters 47 | STDRDL 48 | productType 49 | C 50 | salableAdamId 51 | 1234806557appExtVrsId 52 | 839637337 53 | 54 | 55 | ''' 56 | 57 | import sys 58 | import codecs 59 | import frida 60 | import plistlib 61 | import os 62 | import base64 63 | 64 | ##### Frida Part 65 | 66 | procName = os.environ.get('ITUNES_PROCESS_NAME', 'iTunes.exe') 67 | session = frida.attach(procName) 68 | #session = frida.attach('iTunesFucked.exe') 69 | 70 | def eprint(*args, **kwargs): 71 | print(*args, file=sys.stderr, **kwargs) 72 | 73 | with codecs.open(os.path.dirname(os.path.realpath(__file__)) + '/get_header_rpc.js', 'r', 'utf-8') as f: 74 | source = f.read() 75 | 76 | script = session.create_script(source) 77 | script.load() 78 | 79 | def on_message(message, data): 80 | if message['type'] == 'send': 81 | eprint("Frida: %s" % message['payload']) 82 | elif message['type'] == 'error': 83 | eprint("Frida error: %s" % message['stack']) 84 | 85 | script.on('message', on_message) 86 | 87 | rpc = script.exports 88 | 89 | 90 | ##### Flask Part 91 | 92 | from flask import Flask, request 93 | from flask import jsonify 94 | 95 | app = Flask(__name__) 96 | 97 | @app.route('/', methods=['GET', 'POST']) 98 | def getHeader(): 99 | hdrUrl = request.args.get('url', "https://p46-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/buyProduct") 100 | retHdrs = rpc.get_header(hdrUrl) 101 | eprint("Got Headers: %s" % retHdrs) 102 | kbsync = retHdrs.pop('kbsync') 103 | guid = retHdrs.pop('X-Guid') 104 | return jsonify({ 105 | "headers": retHdrs, 106 | "kbsync": kbsync, 107 | "guid": guid 108 | }) 109 | 110 | if __name__ == '__main__': 111 | app.run(host='0.0.0.0', port=9000) 112 | 113 | session.detach() -------------------------------------------------------------------------------- /workflow_helper/iTunesDownload/get_header_rpc.js: -------------------------------------------------------------------------------- 1 | var getHeader = null; 2 | function init() { 3 | var CFURLCreateWithBytes = new NativeFunction(Module.findExportByName('CoreFoundation','CFURLCreateWithBytes'), 'pointer', ['pointer', 'pointer', 'int64', 'int64', 'int64']) 4 | var CFDictionaryGetCount = new NativeFunction(Module.findExportByName('CoreFoundation','CFDictionaryGetCount'), 'int64', ['pointer']) 5 | var CFCopyDescription = new NativeFunction(Module.findExportByName('CoreFoundation','CFCopyDescription'), 'pointer', ['pointer']) 6 | var CFStringGetCStringPtr = new NativeFunction(Module.findExportByName('CoreFoundation','CFStringGetCStringPtr'), 'pointer', ['pointer', 'uint64']) 7 | var CFStringGetCString = new NativeFunction(Module.findExportByName('CoreFoundation','CFStringGetCString'), 'int8', ['pointer', 'pointer', 'int64', 'int64']) 8 | var CFStringGetLength = new NativeFunction(Module.findExportByName('CoreFoundation','CFStringGetLength'), 'int64', ['pointer']) 9 | var CFDictionaryGetCount = new NativeFunction(Module.findExportByName('CoreFoundation','CFDictionaryGetCount'), 'int64', ['pointer']) 10 | var CFDictionaryGetKeysAndValues = new NativeFunction(Module.findExportByName('CoreFoundation','CFDictionaryGetKeysAndValues'), 'void', ['pointer', 'pointer', 'pointer']) 11 | var CFDataGetBytePtr = new NativeFunction(Module.findExportByName('CoreFoundation','CFDataGetBytePtr'), 'pointer', ['pointer']) 12 | var CFDataGetLength = new NativeFunction(Module.findExportByName('CoreFoundation','CFDataGetLength'), 'uint64', ['pointer']) 13 | 14 | var readCFStr = (cfs) => { 15 | var cfslen = CFStringGetLength(cfs) 16 | var cfsBuf = Memory.alloc(cfslen + 1) 17 | CFStringGetCString(cfs, cfsBuf, cfslen + 1, 134217984) 18 | return Memory.readUtf8String(cfsBuf, cfslen) 19 | } 20 | 21 | var readCFDict = (cfdict) => { 22 | var dictCount = CFDictionaryGetCount(cfdict) 23 | var dictKeys = Memory.alloc(dictCount * 8) 24 | var dictValues = Memory.alloc(dictCount * 8) 25 | CFDictionaryGetKeysAndValues(cfdict, dictKeys, dictValues) 26 | var ret = {} 27 | for (var i = 0; i < dictCount; i++) { 28 | var k = readCFStr(dictKeys.add(8 * i).readPointer()) 29 | var v = readCFStr(dictValues.add(8 * i).readPointer()) 30 | ret[k] = v 31 | } 32 | return ret 33 | } 34 | 35 | var readCFData = (cfd) => { 36 | var cfdlen = CFDataGetLength(cfd) 37 | var cfdbuf = CFDataGetBytePtr(cfd) 38 | return cfdbuf.readByteArray(cfdlen) 39 | } 40 | 41 | var itunesBase = Process.enumerateModulesSync()[0].base 42 | var cfAllocator = itunesBase.add(0x22A8448).readPointer() 43 | var kbsyncContext = itunesBase.add(0x22A6BBC).readU32() 44 | var prepareAppleHdrWrap = new NativeFunction(itunesBase.add(0x87FFC0), 'pointer',['pointer','pointer','pointer']); 45 | var get_cookie_val = new NativeFunction(itunesBase.add(0xBDE500), 'pointer',['pointer']); 46 | var get_kbsync = new NativeFunction(itunesBase.add(0x74D210), 'pointer',['uint32', 'uint32', 'pointer']); 47 | 48 | getHeader = function (url) { 49 | var GlobalContext = itunesBase.add(0x22A9B18).readPointer() 50 | var otpGlobalContext = GlobalContext 51 | if (!GlobalContext.isNull()) { 52 | otpGlobalContext = GlobalContext.add(62716).readPointer() 53 | } 54 | 55 | var otpContext = Memory.alloc(128) 56 | otpContext.writePointer(otpGlobalContext) 57 | otpContext.add(8).writeU64(0) 58 | otpContext.add(16).writeU64(0) 59 | otpContext.add(24).writeU16(1) 60 | otpContext.add(26).writeU8(0) 61 | otpContext.add(27).writeU8(1) // include X-Guid 62 | otpContext.add(28).writeU8(1) // include ADIV1 Hdrs 63 | otpContext.add(32).writeU32(0) 64 | otpContext.add(36).writeU16(0) 65 | otpContext.add(40).writeU32(0) 66 | otpContext.add(48).writeU64(0) 67 | otpContext.add(64).writeU64(0) 68 | otpContext.add(72).writeU64(0) 69 | otpContext.add(84).writeU64(0) 70 | otpContext.add(112).writeU8(1) 71 | 72 | var urlBuf = Memory.allocUtf8String(url) 73 | var urlData = CFURLCreateWithBytes(cfAllocator, urlBuf, url.length, 0x8000100, 0) 74 | 75 | var kbsync = get_kbsync(kbsyncContext, 1, otpGlobalContext) 76 | 77 | var cookieCFStr = get_cookie_val(urlData) 78 | var hdrDict = prepareAppleHdrWrap(NULL, urlData, otpContext) 79 | //hdrdesc = CFCopyDescription(hdrDict) 80 | //console.log(readCFStr(hdrdesc)) 81 | 82 | var cookieStr = readCFStr(cookieCFStr) 83 | var hdrOutput = readCFDict(hdrDict) 84 | hdrOutput['Cookie'] = cookieStr 85 | 86 | var kbSyncData = readCFData(kbsync) 87 | hdrOutput['kbsync'] = [...new Uint8Array(kbSyncData)].map(x => x.toString(16).padStart(2, '0')).join(''); 88 | 89 | return hdrOutput 90 | }; 91 | } 92 | 93 | rpc.exports = { 94 | getHeader: (url) => { 95 | if (!getHeader) { 96 | init() 97 | } 98 | return getHeader(url); 99 | }, 100 | }; -------------------------------------------------------------------------------- /workflow_helper/iTunesInstall/install_itunes.bat: -------------------------------------------------------------------------------- 1 | cd %~dp0 2 | 3 | curl -LO https://secure-appldnld.apple.com/itunes12/091-87819-20180912-69177170-B085-11E8-B6AB-C1D03409AD2A6/iTunes64Setup.exe 4 | iTunes64Setup.exe /extract 5 | 6 | @REM start /wait msiexec.exe /i AppleApplicationSupport.msi /qn 7 | start /wait msiexec.exe /i AppleApplicationSupport64.msi /qn 8 | @REM start /wait msiexec.exe /i AppleMobileDeviceSupport64.msi /qn 9 | @REM start /wait msiexec.exe /i AppleSoftwareUpdate.msi /qn 10 | @REM start /wait msiexec.exe /i Bonjour64.msi /qn 11 | start /wait msiexec.exe /i iTunes64.msi /qn 12 | 13 | python3 patch_itunes.py -------------------------------------------------------------------------------- /workflow_helper/iTunesInstall/patch_itunes.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # for debug 4 | P = r"C:\Program Files\iTunes\iTunes1.exe" 5 | if not os.path.exists(P): 6 | # for production 7 | P = r"C:\Program Files\iTunes\iTunes.exe" 8 | 9 | with open(P, 'rb') as f: 10 | data = f.read() 11 | 12 | data = bytearray(data) 13 | 14 | # Patch passwordSettings (actually useless) 15 | data[0x77daf4:0x77daf4+5] = b'\xBF\x03\x00\x00\x00' 16 | data[0x77db7e:0x77db7e+2] = b'\xEB\x0A' 17 | 18 | # Patch signIn reason to serverDialog 19 | data[0x7a9ed4:0x7a9ed4+6] = b'\xB9\x23\xFF\xFF\xFF\x90' 20 | 21 | with open(P, 'wb') as f: 22 | f.write(data) -------------------------------------------------------------------------------- /workflow_helper/itunes_auto_login.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import time 3 | from pywinauto.application import Application 4 | from win32con import * 5 | import sys 6 | 7 | ACCOUNT = sys.argv[1] 8 | PASSWORD = sys.argv[2] 9 | 10 | print("Launching iTunes...") 11 | 12 | def initITunes(): 13 | subprocess.call('taskkill /f /im iTunes*', shell=True) 14 | 15 | app = Application().start(r"C:\Program Files\iTunes\iTunes.exe") 16 | app.wait_cpu_usage_lower() 17 | time.sleep(8) 18 | 19 | def debugTopWin(): 20 | topwin = app.top_window().wait('exists') 21 | texts = [] 22 | texts += topwin.texts() 23 | for c in topwin.iter_children(): 24 | texts += c.texts() 25 | print("-- Cur top win: %s, texts: %s" % (topwin, texts)) 26 | 27 | def cleanAllDialog(): 28 | while True: 29 | topwin = app.top_window().wait('exists') 30 | if 'Dialog' in topwin.class_name(): 31 | print(" Closing dialog %s" % topwin.window_text()) 32 | app.top_window().Button0.click() 33 | elif 'Tour' in topwin.window_text(): 34 | print(" Closing Window %s" % topwin.window_text()) 35 | topwin.close() 36 | else: 37 | break 38 | 39 | app.wait_cpu_usage_lower() 40 | time.sleep(5) 41 | 42 | # Click all first-time dialogs (like License Agreements, missing audios) 43 | cleanAllDialog() 44 | 45 | # Calm down a bit before main window operations 46 | app.wait_cpu_usage_lower() 47 | debugTopWin() 48 | 49 | # Click main window's first-time question ("No thanks" button) 50 | try: 51 | buttonText = app.iTunes.Button11.wait('ready').window_text() 52 | print('Button11 text is: %s' % buttonText) 53 | if 'Search' not in buttonText: 54 | print("Clicked 'No Thanks' Button!") 55 | app.iTunes.Button11.click_input() 56 | app.wait_cpu_usage_lower() 57 | time.sleep(4) 58 | else: 59 | raise Exception('stub') 60 | except: 61 | print("Not founding 'No Thanks' Button, passing on...") 62 | 63 | 64 | # Start logging in by clicking toolbar menu "Account" 65 | print("Clicking Account menu...") 66 | app.iTunes.Application.Static3.click() 67 | app.wait_cpu_usage_lower() 68 | time.sleep(3) 69 | 70 | debugTopWin() 71 | 72 | # Detect whether we have "&S" in popup, which refers to "Sign in" 73 | popup = app.PopupMenu 74 | if '&S' not in popup.menu().item(1).text(): 75 | popup.close() 76 | raise Exception("Already logged in!") 77 | 78 | print("Signin menu presented, clicking to login!") 79 | # not log in 80 | popup.menu().item(1).click_input() 81 | app.wait_cpu_usage_lower() 82 | time.sleep(8) 83 | debugTopWin() 84 | 85 | for i in range(15): 86 | dialog = app.top_window() 87 | dialogWrap = dialog.wait('ready') 88 | assert dialogWrap.friendly_class_name() == 'Dialog' 89 | time.sleep(1.0) 90 | try: 91 | if dialogWrap.window_text() == 'iTunes' \ 92 | and dialog.Edit1.wait('ready').window_text() == 'Apple ID' \ 93 | and dialog.Edit2.wait('ready').window_text() == 'Password' \ 94 | and dialog.Button1.wait('exists').window_text() == '&Sign In': 95 | break 96 | except Exception as e: 97 | continue 98 | else: 99 | raise Exception("Failed to find login window in 15 iterations!") 100 | app.wait_cpu_usage_lower() 101 | 102 | print("Setting login dialog edit texts") 103 | 104 | appleid_Edit = dialog.Edit1 105 | appleid_Edit.wait('ready') 106 | appleid_Edit.click_input() 107 | appleid_Edit.type_keys(ACCOUNT) 108 | appleid_Edit.set_edit_text(ACCOUNT) 109 | time.sleep(3) 110 | 111 | pass_Edit = dialog.Edit2 112 | pass_Edit.wait('ready') 113 | pass_Edit.click_input() 114 | pass_Edit.type_keys(PASSWORD) 115 | pass_Edit.set_edit_text(PASSWORD) 116 | time.sleep(3) 117 | 118 | print("Clicking login button!") 119 | loginButton = dialog.Button1 120 | loginButton.wait('ready') 121 | # click multiple times as pywinauto seems to have some bug 122 | loginButton.click() 123 | time.sleep(0.5) 124 | try: 125 | loginButton.click() 126 | time.sleep(0.5) 127 | loginButton.click_input() 128 | except: 129 | pass 130 | 131 | 132 | print("Waiting login result...") 133 | time.sleep(10) 134 | debugTopWin() 135 | 136 | if app.top_window().handle == dialogWrap.handle: 137 | raise Exception("Failed to trigger Login button!") 138 | elif app.top_window().window_text() == 'Verification Failed': 139 | raise Exception("Verification Failed: %s" % app.top_window().Static2.window_text()) 140 | 141 | 142 | # Finish & Cleanup 143 | print("Waiting all dialogs to finish") 144 | cleanAllDialog() 145 | 146 | 147 | for init_i in range(3): 148 | try: 149 | initITunes() 150 | break 151 | except Exception as e: 152 | print("Init iTunes %d: Failed with %s" % (init_i, e)) 153 | import traceback; traceback.print_exc() 154 | time.sleep(8) 155 | 156 | print("Init iTunes Successfully!") --------------------------------------------------------------------------------