molvqingtai

molvqingtai

JavaScript Developer and TypeScript Gymnast.

Revive banned QQ groups using Electron version of QQ.

A QQ group that I have been a part of since college was recently shut down, and I have some sentimental attachment to this group. I used to chat in the group when I had free time, so I feel a bit regretful.

I thought about whether I could start from the QQ application itself and use OCR to extract the QQ numbers of the group members from the friend list, and then send emails to them using the format [email protected] to guide them to join a new group.

But I am unable to implement this solution.

image

At that time, I clicked the "Exit Group Chat" button in the dialog prompt when the QQ group was blocked, so the group no longer exists in the QQ group list, and I cannot operate the UI. Therefore, I can only find a device that has logged into the QQ group and has not clicked the "Exit Group Chat" button in the dialog prompt. Unfortunately, I don't have one...

Since OCR doesn't work, I can only start from the local database. I searched online and found that it is more difficult than I imagined.

First, the local db file of the QQ application is encrypted. After a lot of effort, I found a post on 52pojie: [Debugging and Reversing] Cracking MacQQ's Local SQLite Database, but the operation difficulty is too high, so I gave up.

Fortunately, a friend in the group reminded me that in the new version of Electron QQ, after synchronizing the data, it will return to the initial state when the group was blocked, and you can right-click on the group to open the group chat window.

Since it uses Electron, we can open the devtools of the chat window using the debugger. This way, we can access the DOM of the member list, right?

Now that we have a plan, let's start:

  1. Download the latest version of Electron QQ.

  2. Use the tool debugtron to start QQ.

  3. Log in to QQ and find the group in the group list. Right-click to open a separate chat window:
    image

  4. In the Sessions interface of the debugtron tool, find the page address that was just opened, click the "respect" button, and the familiar devtools panel will appear.

image

  1. With the devtools, we can use JavaScript to manipulate the recorded member list. Here is the code:
void (async () => {
  const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
  /**
   * Frame timer
   * @param  {Funct} func     [Callback function]
   * @param  {Number} timeout [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 async selector
   * @param  {String} selector [CSS selector]
   * @param  {Number} timeout  [Timeout]
   * @return {Promise}         [Target]
   */
  const asyncQuerySelector = (selector, timeout) => {
    return asyncLoopTimer(() => {
      return document.querySelector(selector)
    }, timeout)
  }

  /**
   * Create element from template string
   * @param {String} template [Element template]
   * @return {Element} Element object
   */
  const createElement = template => {
    return new Range().createContextualFragment(template).firstElementChild
  }

  /** Download */
  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' // Member list DOM
  const USER_CARD_REF_CLASS = '.buddy-profile' // Member information card
  const USER_NAME_REF_CLASS = '.buddy-profile__header-name' // Member name
  const USER_QQ_REF_CLASS = '.buddy-profile__header-uid' // Member 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)
})

The above code roughly follows this process: simulate scrolling through the member list, then click to open the information card one by one, record the information of the members, and finally download it as a JSON file.

Although this article and the title have some discrepancies, it is not a true "recovery". If your group is suddenly shut down one day, this can be a feasible solution to help you recover some losses.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.