前段時間、何年も続いた QQ グループが和諧されました。大学の頃からこのグループに参加していたので、少なからず愛着があります。普段は暇な時にグループで少しおしゃべりしていたので、少し残念に思っています。
QQ アプリ自体からアプローチできないか、OCR を使ってグループメンバーのリストを取得し、メンバーの QQ 番号を手に入れ、[email protected] の形式で一斉送信して新しいグループに誘導できないかと考えました。
しかし、この案は私には実行できません。
当時、封鎖された QQ グループのダイアログの「退出群聊」ボタンをクリックしました。そのグループは QQ グループリストにはもう存在せず、UI を操作することもできません。したがって、その QQ グループにログインしたことがあるデバイスを見つけ、「退出群聊」ボタンをクリックしていないものを探すしかありません。残念ながら、私はそれを持っていません...
OCR が使えないので、ローカルデータベースからアプローチするしかありません。ネットで調べてみましたが、思ったより難易度が高いです。
まず、QQ アプリのローカル db ファイルは暗号化されています。やっとのことで吾愛破解で見つけた投稿: [调试逆向] 撬开 MacQQ 的本地 SQLite 数据库 ですが、操作の難易度が高すぎて、諦めました。
幸いなことに、グループメンバーの助言で、新版の Electron QQ はデータを同期した後、封鎖された時の初期状態に戻ります。右クリックしてそのグループのチャットウィンドウを開くことができます。
ハハ、Electron を使用しているので、デバッガーの方法でチャットウィンドウの devtools を開けば、グループメンバーリストの DOM を取得できるのではないでしょうか?
アイデアが浮かんだので、操作を開始します:
-
最新の Electron 版 QQ をダウンロード
-
debugtron というツールを使って QQ を起動
-
QQ にログインし、グループリストからそのグループを見つけ、右クリックして個別のチャットウィンドウを開く:
-
debugtron ツールの Sessions インターフェースで、先ほど開いたページのアドレスを見つけ、respect ボタンをクリックすると、馴染みのある devtools パネルが表示されます。
- 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 ファイルとしてダウンロードすることです。
この記事とタイトルには少し食い違いがありますが、実際には「復元」ではありません。もしあなたのグループが突然和諧された場合、実行可能な解決策の一つとして、少しでも損失を取り戻す手助けになればと思います。