rs75 发表于 2020-10-9 12:16:33

实时抓取qq聊天窗口的消息

tx在8月初将酷q等一切工具搞掉了, 但是我的砖还得搬, 于是临时写了一个抓取消息的脚本。
原理是通过IAccessible接口抓取聊天窗口的消息再处理得到每条消息, 不才, 现成资料是用ahk调用dll然后我就直接用了而没有折腾改成Python。
使用方法:
三个文件放一起,
os.system("start QQ抓取.ahk xxx")   xxx改成要监控的窗口名, 多个窗口就复制这行
destination = ('127.0.0.1', 5888)      用socket把消息发到这个地址

import os
import threading
import socket
import json
import re


def parser(new_text, l_old_text):
    new_text = re.sub(r"\n {2}(?=.+\(\d+\) ( |202\d-\d\d-\d\d )\d\d:\d\d:\d\d)", '-cut-', new_text.replace('\r', '\n'))# \r改\n, 在每个用户标识前将'\n'替换成分隔符号-cut-(不直接split是因为空格开头的消息会出错)
    l_new_text = new_text.split('-cut-')
    for text in l_new_text:
      if text not in l_old_text:
            QQID = re.search(r"(?<=\()\d+(?=\))", text).group()
            message = re.search(r"(?<=\d\d:\d\d:\d\d\n ).+", text, re.DOTALL).group()
            client = socket.socket()
            try:
                client.connect(destination)
                client.send(json.dumps({'type': 'message', 'user_id': QQID, 'message': message}).encode('utf8'))
            except Exception:
                print("异常 - 发送失败")
            client.close()
    return l_new_text


def handle_receive(client):
    receive = client.recv(65536).decode('utf8')
    if "ahk-" in receive:
      name = receive
      l_old_text = []
      print('已连接:', receive)
      while True:
            try:
                all_text = client.recv(65536).decode('utf8')
                if all_text:
                  l_old_text = parser(all_text, l_old_text)# 不要前面空格和最后面\r空格
                else:
                  client.close()
                  print('\n已断开:', name)
                  break
            except Exception as e:
                print('异常 - 处理消息', name, e)


if __name__ == '__main__':
    server = socket.socket()
    server.bind(('', 5886))
    server.listen(5)
    os.system("start QQ抓取.ahk xxx")
    destination = ('127.0.0.1', 5888)

    while True:
      try:
            client = server.accept()
            thread = threading.Thread(target=handle_receive, args=(client, ))
            thread.start()
      except Exception as e:
            print('异常 - 主循环', e)




QQ抓取.ahk:
#Persistent
#SingleInstance,force
#Include Socket.ahk
SetBatchLines,-1

Acc_Init()
{
      static h
      If Not h
                h:=DllCall("LoadLibrary","Str","oleacc","Ptr")
}
Acc_ObjectFromWindow(hWnd, idObject = 0)
{
      Acc_Init()
      If DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject&=0xFFFFFFFF, "Ptr", -VarSetCapacity(IID,16)+NumPut(idObject==0xFFFFFFF0?0x46000000000000C0:0x719B3800AA000C81,NumPut(idObject==0xFFFFFFF0?0x0000000000020400:0x11CF3C3D618736E0,IID,"Int64"),"Int64"), "Ptr*", pacc)=0
      Return ComObjEnwrap(9,pacc,1)
}
Acc_Query(Acc) {
      Try Return ComObj(9, ComObjQuery(Acc,"{618736e0-3c3d-11cf-810c-00aa00389b71}"), 1)
}
Acc_Children(Acc) {
      If ComObjType(Acc,"Name") != "IAccessible"
                ErrorLevel := "Invalid IAccessible Object"
      Else {
                Acc_Init(), cChildren:=Acc.accChildCount, Children:=[]
                If DllCall("oleacc\AccessibleChildren", "Ptr",ComObjValue(Acc), "Int",0, "Int",cChildren, "Ptr",VarSetCapacity(varChildren,cChildren*(8+2*A_PtrSize),0)*0+&varChildren, "Int*",cChildren)=0 {
                        Loop %cChildren%
                              i:=(A_Index-1)*(A_PtrSize*2+8)+8, child:=NumGet(varChildren,i), Children.Insert(NumGet(varChildren,i-8)=9?Acc_Query(child):child), NumGet(varChildren,i-8)=9?ObjRelease(child):
                        Return Children.MaxIndex()?Children:
                } Else
                        ErrorLevel := "AccessibleChildren DllCall Failed"
      }
}
GetElementByName(AccObj, name) {
   if (AccObj.accName(0) = name)
      return AccObj
   
   for k, v in Acc_Children(AccObj)
      if IsObject(obj := GetElementByName(v, name))
         return obj
}


title = %1%; start QQ抓取.ahk c

client := new SocketTCP()
client.connect("127.0.0.1",5886)
client.sendText("ahk-" title)

hwnd := WinExist(title)
window := Acc_ObjectFromWindow(hwnd)
widget := GetElementByName(window, "消息")

old_text := widget.accValue(0)

Loop, {
    sleep, 100
    current_text := widget.accValue(0)
    if (current_text != old_text){
      client.sendText(current_text)
      old_text := current_text
    }
}



Socket.ahk:
;~ https://autohotkey.com/board/topic/94376-socket-class-%c3%bcberarbeitet/
class Socket
{
static __eventMsg := 0x9987

__New(s=-1)
{
    static init
    if (!init)
    {
      DllCall("LoadLibrary", "str", "ws2_32", "ptr")
      VarSetCapacity(wsadata, 394+A_PtrSize)
      DllCall("ws2_32\WSAStartup", "ushort", 0x0000, "ptr", &wsadata)
      DllCall("ws2_32\WSAStartup", "ushort", NumGet(wsadata, 2, "ushort"), "ptr", &wsadata)
      OnMessage(Socket.__eventMsg, "SocketEventProc")
      init := 1
    }
    this.socket := s
}
__Delete()
{
    this.disconnect()
}
__Get(k, v)
{
    if (k="size")
      return this.msgSize()
}
connect(host, port)
{
    if ((this.socket!=-1) || (!(faddr := next := this.__getAddrInfo(host, port))))
      return 0
    while (next)
    {
      sockaddrlen := NumGet(next+0, 16, "uint")
      sockaddr := NumGet(next+0, 16+(2*A_PtrSize), "ptr")
      if ((this.socket := DllCall("ws2_32\socket", "int", NumGet(next+0, 4, "int"), "int", this.__socketType, "int", this.__protocolId, "ptr"))!=-1)
      {
      if ((r := DllCall("ws2_32\WSAConnect", "ptr", this.socket, "ptr", sockaddr, "uint", sockaddrlen, "ptr", 0, "ptr", 0, "ptr", 0, "ptr", 0, "int"))=0)
      {
          DllCall("ws2_32\freeaddrinfo", "ptr", faddr)
          return Socket.__eventProcRegister(this, 0x21)
      }
      this.disconnect()
      }
      next := NumGet(next+0, 16+(3*A_PtrSize), "ptr")
    }
    this.lastError := DllCall("ws2_32\WSAGetLastError")
    return 0
}
bind(host, port)
{
    if ((this.socket!=-1) || (!(faddr := next := this.__getAddrInfo(host, port))))
      return 0
    while (next)
    {
      sockaddrlen := NumGet(next+0, 16, "uint")
      sockaddr := NumGet(next+0, 16+(2*A_PtrSize), "ptr")
      if ((this.socket := DllCall("ws2_32\socket", "int", NumGet(next+0, 4, "int"), "int", this.__socketType, "int", this.__protocolId, "ptr"))!=-1)
      {
      if (DllCall("ws2_32\bind", "ptr", this.socket, "ptr", sockaddr, "uint", sockaddrlen, "int")=0)
      {
          DllCall("ws2_32\freeaddrinfo", "ptr", faddr)
          return Socket.__eventProcRegister(this, 0x29)
      }
      this.disconnect()
      }
      next := NumGet(next+0, 16+(3*A_PtrSize), "ptr")
    }
    this.lastError := DllCall("ws2_32\WSAGetLastError")
    return 0
}
listen(backlog=32)
{
    return (DllCall("ws2_32\listen", "ptr", this.socket, "int", backlog)=0) ? 1 : 0
}
accept()
{
    if ((s := DllCall("ws2_32\accept", "ptr", this.socket, "ptr", 0, "int", 0, "ptr"))!=-1)
    {
      newsock := new Socket(s)
      newsock.__protocolId := this.__protocolId
      newsock.__socketType := this.__socketType
      Socket.__eventProcRegister(newsock, 0x21)
      return newsock
    }
    return 0
}
disconnect()
{
    Socket.__eventProcUnregister(this)
    DllCall("ws2_32\closesocket", "ptr", this.socket, "int")
    this.socket := -1
    return 1
}
msgSize()
{
    VarSetCapacity(argp, 4, 0)
    if (DllCall("ws2_32\ioctlsocket", "ptr", this.socket, "uint", 0x4004667F, "ptr", &argp)!=0)
      return 0
    return NumGet(argp, 0, "int")
}
send(addr, length)
{
    if ((r := DllCall("ws2_32\send", "ptr", this.socket, "ptr", addr, "int", length, "int", 0, "int"))<=0)
      return 0
    return r
}
sendText(msg, encoding="UTF-8")
{
    VarSetCapacity(buffer, length := (StrPut(msg, encoding)*(((encoding="utf-16")||(encoding="cp1200")) ? 2 : 1))-1)
    StrPut(msg, &buffer, encoding)
    return this.send(&buffer, length)
}
recv(byref buffer, wait=1)
{
    while ((wait) && ((length := this.msgSize())=0))
      sleep, 100
    if (length)
    {
      VarSetCapacity(buffer, length)
      if ((r := DllCall("ws2_32\recv", "ptr", this.socket, "ptr", &buffer, "int", length, "int", 0))<=0)
      return 0
      return r
    }
    return 0
}
recvText(wait=1, encoding="UTF-8")
{
    if (length := this.recv(buffer, wait))
      return StrGet(&buffer, length, encoding)
    return
}
__getAddrInfo(host, port)
{
    a := ["127.0.0.1", "0.0.0.0", "255.255.255.255", "::1", "::", "FF00::"]
    conv := {localhost:a, addr_loopback:a, inaddr_loopback:a, addr_any:a, inaddr_any:a, addr_broadcast:a
    , inaddr_broadcast:a, addr_none:a, inaddr_none:a, localhost6:a, addr_loopback6:a, inaddr_loopback6:a
    , addr_any6:a, inaddr_any:a, addr_broadcast6:a, inaddr_broadcast6:a, addr_none6:a, inaddr_none6:a}
    if (conv)
      host := conv
    VarSetCapacity(hints, 16+(4*A_PtrSize), 0)
    NumPut(this.__socketType, hints, 8, "int")
    NumPut(this.__protocolId, hints, 12, "int")
    if ((r := DllCall("ws2_32\getaddrinfo", "astr", host, "astr", port, "ptr", &hints, "ptr*", next))!=0)
    {
      this.lastError := DllCall("ws2_32\WSAGetLastError")
      return 0
    }
    return next
}
__eventProcRegister(obj, msg)
{
    a := SocketEventProc(0, 0, "register", 0)
    a := obj
    return (DllCall("ws2_32\WSAAsyncSelect", "ptr", obj.socket, "ptr", A_ScriptHwnd, "uint", Socket.__eventMsg, "uint", msg)=0) ? 1 : 0
}
__eventProcUnregister(obj)
{
    a := SocketEventProc(0, 0, "register", 0)
    a.remove(obj.socket)
    return (DllCall("ws2_32\WSAAsyncSelect", "ptr", obj.socket, "ptr", A_ScriptHwnd, "uint", 0, "uint", 0)=0) ? 1 : 0
}
}
SocketEventProc(wParam, lParam, msg, hwnd)
{
global Socket
static a := []
Critical
if (msg="register")
    return a
if (msg=Socket.__eventMsg)
{
    if (!isobject(a))
      return 0
    if ((lParam & 0xFFFF) = 1)
      return a.onRecv(a)
    else if ((lParam & 0xFFFF) = 8)
      return a.onAccept(a)
    else if ((lParam & 0xFFFF) = 32)
    {
      a.socket := -1
      return a.onDisconnect(a)
    }
    return 0
}
return 0
}

class SocketTCP extends Socket
{
static __protocolId := 6 ;IPPROTO_TCP
static __socketType := 1 ;SOCK_STREAM
}

class SocketUDP extends Socket
{
static __protocolId := 17 ;IPPROTO_UDP
static __socketType := 2 ;SOCK_DGRAM

enableBroadcast()
{
    VarSetCapacity(optval, 4, 0)
    NumPut(1, optval, 0, "uint")
    if (DllCall("ws2_32\setsockopt", "ptr", this.socket, "int", 0xFFFF, "int", 0x0020, "ptr", &optval, "int", 4)=0)
      return 1
    return 0
}
disableBroadcast()
{
    VarSetCapacity(optval, 4, 0)
    if (DllCall("ws2_32\setsockopt", "ptr", this.socket, "int", 0xFFFF, "int", 0x0020, "ptr", &optval, "int", 4)=0)
      return 1
    return 0
}
}

weixing123 发表于 2020-10-18 17:22:59

如果做成一个实时转发群消息的功能就更好了。比如从A群转到B群

weixing123 发表于 2020-10-18 17:23:31

作者有什么想法吗?
页: [1]
查看完整版本: 实时抓取qq聊天窗口的消息