iksm_sessionをadbを介して取得するだけのpythonスクリプト

概要

以下のWebサイトに記載されている手順をある程度自動化したもの。
tryfreesoft.at.webry.info

このスクリプトがやること

  • adb(Android Debug Bridge)でroot権限でandroid端末に接続する
  • 接続に失敗したら、Nox.exe(NoxPlayer; Androidエミュレーターの一種)を自前で起動してみる
  • 接続に成功したら、Android端末中のNintendo Switch OnlineのWebViewが保持しているクッキーをadbで取り出す
  • 取り出したクッキーの有効期限を確認し、期限切れの場合は再ログインを促す
  • 申し訳程度のGUI付き

f:id:vidamrot:20190707145924p:plain
iksm_session getter

スクリプトを実行する前の前準備

筆者はWindows 7Androidエミュレーターの一種であるNoxPlayerをインストールして使用している。NoxPlayerを用いる場合の特有の注意点は次のとおり。

  • Root化された端末ではNintendo Switch Onlineが起動できないため、ウィンドウの右上の歯車ボタン→通常設定 からRoot起動をオフにしておく。
  • マルチインスタンスマネージャーにより複数の仮想端末を管理している場合、接続したい端末のポート番号やエイリアス名を事前に調べておく必要がある。以下のサイトが非常に参考になる。

news.mynavi.jp

スクリプト本体

import subprocess
import time
import datetime
import threading
import sqlite3
import tkinter
from contextlib import closing

# android のローカルIPアドレス、ポート番号
ADDR_ANDROID = "127.0.0.1:62001"
# androidエミュレーターを起動するためのコマンド
CMD_ANDROID = "C:\\Program Files (x86)\\Nox\\bin\\Nox.exe -clone:Nox_0"

textBox = None
labelText = None



def guiStart():
    global textBox
    global labelText

    root = tkinter.Tk()
    root.title("iksm_session getter")
    root.geometry("400x120")

    mainFrame = tkinter.Frame(root, bg="white")
    mainFrame.pack(fill = tkinter.BOTH, expand = True)

    textFrame = tkinter.Frame(mainFrame, width=400, height=40, bg="white")
    textFrame.pack()

    labelFrame = tkinter.Frame(mainFrame, bg="lightgray",  relief = 'sunken', borderwidth = 2)
    labelFrame.pack(fill = tkinter.BOTH, expand = True)

    textBox = tkinter.Text(textFrame, height=1, width=48, bg="white")
    textBox.pack()

    button = tkinter.Button(textFrame, text=u'iksm_session取得', height=1)
    button.bind("<Button>",buttonClick)
    button.pack()
    
    # ラベルの文字列を変更したい場合はStringVarを割り当てる。
    labelText = tkinter.StringVar()
    label = tkinter.Label(labelFrame, textvariable = labelText, height=2, anchor='n')
    label.pack(fill = tkinter.BOTH, expand = True)

    root.mainloop()

def buttonClick(e):
    print(e)
    # バックグラウンドの処理は別スレッドで行わないと GUI が固まる。
    threading.Thread(target=backStart).start()

def backStart():
    global textBox
    global labelText

    # android端末とroot権限で接続する。
    # 接続に失敗したら端末を自前で起動しようとする。
    while True:
        subprocess.call('adb connect ' + ADDR_ANDROID)

        # android端末が起動されていない場合、adb root で 1 が返ってくる。
        returncode = subprocess.call('adb root')
        print("return code = ", returncode)
        if returncode == 1:
            labelText.set("Android端末に接続できません。\nNoxを起動します。")
            returncode = subprocess.Popen(CMD_ANDROID)
            print("return code = ", returncode)
        else:
            labelText.set("Android端末に接続完了。")
            break
        time.sleep(30)
    
    #Nintendo Switch Online の WebView のクッキーを5秒に一度取り出す。
    while True:
        subprocess.call('adb pull /data/data/com.nintendo.znca/app_webview/Cookies Cookies.db')
        labelText.set("クッキー取得完了。")

        with closing(sqlite3.connect('Cookies.db')) as conn:
            c = conn.cursor()
            r = c.execute('select expires_utc, value from cookies where name = "iksm_session"').fetchall()[0]
            print(r)
            
            # chrome のクッキーは 1601/01/01 を起点としたUTC時刻で保存されている。
            JST = datetime.timezone(datetime.timedelta(hours=+9), 'JST')
            now_utc = datetime.datetime.now(JST)
            d = datetime.datetime(1970, 1, 1, 0, 0, 0, 0) - datetime.datetime(1601, 1, 1, 0, 0, 0, 0)
            expires_utc = datetime.datetime.fromtimestamp(r[0]/1000000  - d.total_seconds(), JST)
            
        if expires_utc > now_utc:
            # 取り出したクッキーが現時点で有効だったらテキストエリアにiksm_sessionを格納しておしまい。
            textBox.delete('1.0', tkinter.END)
            textBox.insert(tkinter.END, r[1])
            labelText.set("iksm_session取得完了。")
            break
        else:
            labelText.set("クッキーの有効期限が切れています。\nNSO にログインし直してください。")

        time.sleep(5)

if __name__ == '__main__':
    guiStart()