molvqingtai

molvqingtai

JavaScript Developer and TypeScript Gymnast.

Electron 版 QQ を利用して封鎖された QQ グループを復活させる

前段時間、何年も続いた QQ グループが和諧されました。大学の頃からこのグループに参加していたので、少なからず愛着があります。普段は暇な時にグループで少しおしゃべりしていたので、少し残念に思っています。

QQ アプリ自体からアプローチできないか、OCR を使ってグループメンバーのリストを取得し、メンバーの QQ 番号を手に入れ、[email protected] の形式で一斉送信して新しいグループに誘導できないかと考えました。

しかし、この案は私には実行できません。

image

当時、封鎖された QQ グループのダイアログの「退出群聊」ボタンをクリックしました。そのグループは QQ グループリストにはもう存在せず、UI を操作することもできません。したがって、その QQ グループにログインしたことがあるデバイスを見つけ、「退出群聊」ボタンをクリックしていないものを探すしかありません。残念ながら、私はそれを持っていません...

OCR が使えないので、ローカルデータベースからアプローチするしかありません。ネットで調べてみましたが、思ったより難易度が高いです。

まず、QQ アプリのローカル db ファイルは暗号化されています。やっとのことで吾愛破解で見つけた投稿: [调试逆向] 撬开 MacQQ 的本地 SQLite 数据库 ですが、操作の難易度が高すぎて、諦めました。

幸いなことに、グループメンバーの助言で、新版の Electron QQ はデータを同期した後、封鎖された時の初期状態に戻ります。右クリックしてそのグループのチャットウィンドウを開くことができます。

ハハ、Electron を使用しているので、デバッガーの方法でチャットウィンドウの devtools を開けば、グループメンバーリストの DOM を取得できるのではないでしょうか?

アイデアが浮かんだので、操作を開始します:

  1. 最新の Electron 版 QQ をダウンロード

  2. debugtron というツールを使って QQ を起動

  3. QQ にログインし、グループリストからそのグループを見つけ、右クリックして個別のチャットウィンドウを開く:
    image

  4. debugtron ツールの Sessions インターフェースで、先ほど開いたページのアドレスを見つけ、respect ボタンをクリックすると、馴染みのある devtools パネルが表示されます。

image

  1. devtools があれば、JavaScript を使ってグループメンバーリストを操作できます。コードは以下の通りです:
void (async () => {
  const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
  /**
   * フレームタイマー
   * @param  {Funct} func     [コールバックメソッド]
   * @param  {Number} timeout [タイムアウト時間]
   * @return {Promise}
   */
  const asyncLoopTimer = (func, timeout = Infinity) => {
    const startTime = performance.now()
    return new Promise(resolve => {
      const timer = async nowTime => {
        cancelAnimationFrame(requestID)
        const data = await func()
        if (data || nowTime - startTime > timeout) {
          resolve(data)
        } else {
          requestID = requestAnimationFrame(timer)
        }
      }
      let requestID = requestAnimationFrame(timer)
    })
  }

  /**
   * CSS 非同期セレクター
   * @param  {String} selector [CSSセレクター]
   * @param  {Number} timeout  [タイムアウト時間]
   * @return {Promise}         [Target]
   */
  const asyncQuerySelector = (selector, timeout) => {
    return asyncLoopTimer(() => {
      return document.querySelector(selector)
    }, timeout)
  }

  /**
   * 文字列テンプレートから要素を作成
   * @param {String} template [要素テンプレート]
   * @return {Element} 要素オブジェクト
   */
  const createElement = template => {
    return new Range().createContextualFragment(template).firstElementChild
  }

  /** ダウンロード */
  const download = (data, name, options) => {
    const href = URL.createObjectURL(new Blob(data), options)
    const a = createElement(`<a href="${href}" download="${name}"></a>`)
    a.click()
  }

  const LIST_REF_CLASS = '.viewport-list__inner' // グループメンバーリスト DOM
  const USER_CARD_REF_CLASS = '.buddy-profile' // グループメンバー情報カード
  const USER_NAME_REF_CLASS = '.buddy-profile__header-name' // グループメンバー名
  const USER_QQ_REF_CLASS = '.buddy-profile__header-uid' // グループメンバー QQ

  const autopilot = (delay = 300) => {
    let userRef = document.querySelector(LIST_REF_CLASS).firstElementChild
    const userList = []
    return async () => {
      userRef.scrollIntoView()
      userRef.firstElementChild.click()
      const cardRef = await asyncQuerySelector(USER_CARD_REF_CLASS, 1000)
      await sleep(delay)
      userList.push({
        name: cardRef.querySelector(USER_NAME_REF_CLASS)?.textContent,
        qq: cardRef.querySelector(USER_QQ_REF_CLASS)?.textContent?.split(' ')[1]
      })

      document.body.click()

      userRef = userRef.nextElementSibling
      console.log('----userList----', userList)
      return userRef ? false : userList
    }
  }

  const userList = await asyncLoopTimer(autopilot(100))

  download([JSON.stringify(userList)], 'users.json', { type: 'application/json' })
})().catch(error => {
  console.error(error)
})

このコードの大まかな流れは、グループメンバーリストをスクロールし、順番に情報カードを開いてメンバーの情報を記録し、最後に JSON ファイルとしてダウンロードすることです。

この記事とタイトルには少し食い違いがありますが、実際には「復元」ではありません。もしあなたのグループが突然和諧された場合、実行可能な解決策の一つとして、少しでも損失を取り戻す手助けになればと思います。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。