From a92cc9e953f2c735f44270c0e4c41088c1e7720b Mon Sep 17 00:00:00 2001 From: yincong Date: Tue, 19 May 2026 15:49:31 +0800 Subject: [PATCH 1/3] new version --- wechat_version/4_1_9_55_mac.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 wechat_version/4_1_9_55_mac.json diff --git a/wechat_version/4_1_9_55_mac.json b/wechat_version/4_1_9_55_mac.json new file mode 100644 index 0000000..8f5e956 --- /dev/null +++ b/wechat_version/4_1_9_55_mac.json @@ -0,0 +1,26 @@ +{ + "textCallbackFuncAddr": "0x268a554", + "sendMessageCallbackFunc": "0x8c61290", + "sendFuncAddr": "0x4be3a84", + "req2bufEnterAddr": "0x39e5fb4", + "req2bufExitAddr": "0x39e70c8", + "imageCallbackFuncAddr": "0x2680f18", + "imgMessageCallbackFunc": "0x8c60d58", + "uploadImageAddr": "0x4d5e838", + "cndOnCompleteAddr": "0x39a25e8", + "buf2RespAddr": "0x3a0b9dc", + "uploadGetCallbackWrapperAddr": "0x4d1d590", + "uploadGetCallbackWrapperFuncAddr": "0x39A1E34", + "uploadOnCompleteAddr": "0x4D1DD48", + "uploadOnCompleteFuncAddr": "0x39A3018", + "startDownloadMedia": "0x4d5f4a0", + "downloadImagAddr": "0x4e81f94", + "downloadFileAddr": "0x4e20ecc", + "downloadVideoAddr": "0x4e3acac", + "videoCallbackFuncAddr": "0x26f04c4", + "videoMessageCallbackFunc": "0x8c63b08", + "replyCallbackFuncAddr": "0x261ef64", + "replyMessageCallbackFunc": "0x8c5e418", + "voiceCallbackFuncAddr": "0x31BF70C", + "voiceMessageCallbackFunc": "0x8CA34E0" +} From ec023bd02378803b688328b39ffc44844f8497e3 Mon Sep 17 00:00:00 2001 From: yincong Date: Mon, 25 May 2026 09:48:39 +0800 Subject: [PATCH 2/3] 58 --- wechat_version/4_1_9_58_mac.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 wechat_version/4_1_9_58_mac.json diff --git a/wechat_version/4_1_9_58_mac.json b/wechat_version/4_1_9_58_mac.json new file mode 100644 index 0000000..c75b38d --- /dev/null +++ b/wechat_version/4_1_9_58_mac.json @@ -0,0 +1,30 @@ +{ + "sendFuncAddr": "0x4bf1b44", + "req2bufEnterAddr": "0x39f18e0", + "req2bufExitAddr": "0x39f29f4", + "buf2RespAddr": "0x3a17308", + + "uploadImageAddr": "0x4d6c8f8", + "cndOnCompleteAddr": "0x39adf14", + "uploadGetCallbackWrapperAddr": "0x4d2b650", + "uploadGetCallbackWrapperFuncAddr": "0x39AD760", + "uploadOnCompleteAddr": "0x4D2BE08", + "uploadOnCompleteFuncAddr": "0x39AE944", + + "startDownloadMedia": "0x4d6d560", + "downloadImagAddr": "0x4e90054", + "downloadFileAddr": "0x4e2ef8c", + "downloadVideoAddr": "0x4e48d6c", + + "textCallbackFuncAddr": "0x2694200", + "sendMessageCallbackFunc": "0x8c714e0", + "imageCallbackFuncAddr": "0x268ABC4", + "imgMessageCallbackFunc": "0x8c70fa8", + "videoCallbackFuncAddr": "0x26FA170", + "videoMessageCallbackFunc": "0x8c73d58", + "replyCallbackFuncAddr": "0x2628C10", + "replyMessageCallbackFunc": "0x8c6e668", + "voiceCallbackFuncAddr": "0x31CB034", + "voiceMessageCallbackFunc": "0x8cb3730" +} + From 56296020aa5dd86cabfddc10a36c2a40d8f31e61 Mon Sep 17 00:00:00 2001 From: yincong Date: Mon, 1 Jun 2026 17:11:46 +0800 Subject: [PATCH 3/3] 4.1.9.58 --- onebot/go.mod | 22 +- onebot/go.sum | 71 +- onebot/http.go | 50 +- onebot/img_builder.go | 30 +- onebot/main.go | 55 +- onebot/msg.go | 14 + onebot/param.go | 57 +- .../reply_msg.pb.go => app_msg.pb.go} | 126 +- .../reply_msg.proto => app_msg.proto} | 16 +- onebot/proto/common_resp.pb.go | 456 +++++++ onebot/proto/common_resp.proto | 43 + onebot/proto/{wxproto => }/img_msg.pb.go | 107 +- onebot/proto/{wxproto => }/img_msg.proto | 12 +- onebot/proto/{wxproto => }/text_msg.pb.go | 62 +- onebot/proto/{wxproto => }/text_msg.proto | 4 +- onebot/proto/{wxproto => }/video_msg.pb.go | 105 +- onebot/proto/{wxproto => }/video_msg.proto | 10 +- onebot/proto/{wxproto => }/wx_msg.pb.go | 84 +- onebot/proto/{wxproto => }/wx_msg.proto | 2 +- onebot/protobuf_handler.go | 11 +- onebot/reply_builder.go | 54 +- onebot/resp_parser.go | 45 + onebot/script.js | 1105 ++++++++--------- onebot/text_builder.go | 23 +- onebot/upload_builder.go | 12 +- onebot/utils.go | 62 + onebot/utils_test.go | 12 + onebot/video_builder.go | 31 +- onebot/worker.go | 136 +- wechat_version/4_1_9_58_mac.json | 16 +- 30 files changed, 1785 insertions(+), 1048 deletions(-) rename onebot/proto/{wxproto/reply_msg.pb.go => app_msg.pb.go} (76%) rename onebot/proto/{wxproto/reply_msg.proto => app_msg.proto} (71%) create mode 100644 onebot/proto/common_resp.pb.go create mode 100644 onebot/proto/common_resp.proto rename onebot/proto/{wxproto => }/img_msg.pb.go (79%) rename onebot/proto/{wxproto => }/img_msg.proto (88%) rename onebot/proto/{wxproto => }/text_msg.pb.go (72%) rename onebot/proto/{wxproto => }/text_msg.proto (86%) rename onebot/proto/{wxproto => }/video_msg.pb.go (80%) rename onebot/proto/{wxproto => }/video_msg.proto (90%) rename onebot/proto/{wxproto => }/wx_msg.pb.go (84%) rename onebot/proto/{wxproto => }/wx_msg.proto (97%) create mode 100644 onebot/resp_parser.go create mode 100644 onebot/utils_test.go diff --git a/onebot/go.mod b/onebot/go.mod index 68760b7..5244df8 100644 --- a/onebot/go.mod +++ b/onebot/go.mod @@ -3,22 +3,24 @@ module github.com/yincongcyincong/weixin-macos/onebot go 1.25.0 require ( - github.com/frida/frida-go v1.0.0 + github.com/frida/frida-go v1.0.2 github.com/gorilla/websocket v1.5.3 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d - github.com/rs/zerolog v1.34.0 + github.com/rs/zerolog v1.35.1 github.com/wdvxdr1123/go-silk v0.0.0-20220304095002-f67345df09ea + google.golang.org/protobuf v1.36.11 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) require ( + github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect - golang.org/x/sys v0.12.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect - modernc.org/libc v1.8.1 // indirect - modernc.org/mathutil v1.2.2 // indirect - modernc.org/memory v1.0.4 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/sys v0.45.0 // indirect + modernc.org/libc v1.72.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect ) diff --git a/onebot/go.sum b/onebot/go.sum index 9bce971..0bd0a42 100644 --- a/onebot/go.sum +++ b/onebot/go.sum @@ -1,42 +1,48 @@ -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/frida/frida-go v1.0.0 h1:Xlq1CB8QSAC6zbOFdjCX0oK8RjQGtdl2yATbUQKROwo= -github.com/frida/frida-go v1.0.0/go.mod h1:O8Dg1YBGfQsBEL1a8x3GURw/JllJrcuvg78ga2OgdM4= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/frida/frida-go v1.0.2 h1:qsWfYM2tcTMXw1iimnsnajhp8QJVT75jgJebtW3rPig= +github.com/frida/frida-go v1.0.2/go.mod h1:O8Dg1YBGfQsBEL1a8x3GURw/JllJrcuvg78ga2OgdM4= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= -github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/wdvxdr1123/go-silk v0.0.0-20220304095002-f67345df09ea h1:sl1pYm1kHtIndckTY8YDt+QFt77vI0JnKHP0U8rZtKc= github.com/wdvxdr1123/go-silk v0.0.0-20220304095002-f67345df09ea/go.mod h1:ecFKZPX81BaB70I6ruUgEwYcDOtuNgJGnjdK+MIl5ko= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -44,10 +50,33 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -modernc.org/libc v1.8.1 h1:y9oPIhwcaFXxX7kMp6Qb2ZLKzr0mDkikWN3CV5GS63o= +modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY= +modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ= +modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/libc v1.8.1/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU= +modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs= modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/onebot/http.go b/onebot/http.go index f4af0ac..172d765 100644 --- a/onebot/http.go +++ b/onebot/http.go @@ -37,6 +37,7 @@ func sendHandler(w http.ResponseWriter, r *http.Request) { sendContent := "" atUserID := "" + var resultChans []chan error for _, v := range req.Message { if v.Type == "text" { sendContent += v.Data.Text @@ -49,12 +50,16 @@ func sendHandler(w http.ResponseWriter, r *http.Request) { } } else if v.Type == "image" || v.Type == "video" { - msgChan <- &SendMsg{ - UserId: req.UserID, - GroupID: req.GroupID, - Content: v.Data.File, - Type: v.Type, + ch := make(chan error, 1) + msg := &SendMsg{ + UserId: req.UserID, + GroupID: req.GroupID, + Content: v.Data.File, + Type: v.Type, + ResultChan: ch, } + msgChan <- msg + resultChans = append(resultChans, ch) } else if v.Type == "reply" { if v.Data.ReplyMessage == nil { Error("reply_message为空") @@ -62,6 +67,11 @@ func sendHandler(w http.ResponseWriter, r *http.Request) { } rm := v.Data.ReplyMessage + // 顶层没有group_id时,从reply_message中取 + if req.GroupID == "" && rm.GroupId != "" { + req.GroupID = rm.GroupId + } + // 提取被回复消息的内容 referContent := "" referMsgType := 1 // 默认text @@ -88,6 +98,7 @@ func sendHandler(w http.ResponseWriter, r *http.Request) { // msgsource需要JSON unescape(双重编码: \\u003c → \u003c → <) msgsource := jsonUnescapeString(rm.MsgResource) + ch := make(chan error, 1) msgChan <- &SendMsg{ UserId: req.UserID, GroupID: req.GroupID, @@ -100,17 +111,35 @@ func sendHandler(w http.ResponseWriter, r *http.Request) { ReferMsgsource: msgsource, ReferDisplayName: displayName, ReferContent: referContent, + ResultChan: ch, } + resultChans = append(resultChans, ch) } } if sendContent != "" { + ch := make(chan error, 1) msgChan <- &SendMsg{ - UserId: req.UserID, - GroupID: req.GroupID, - Content: sendContent, - Type: "text", - AtUser: strings.TrimRight(atUserID, ","), + UserId: req.UserID, + GroupID: req.GroupID, + Content: sendContent, + Type: "text", + AtUser: strings.TrimRight(atUserID, ","), + ResultChan: ch, + } + resultChans = append(resultChans, ch) + } + + // 等待所有消息发送完成 + for _, ch := range resultChans { + if err := <-ch; err != nil { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(map[string]any{ + "status": "failed", + "error": err.Error(), + }) + return } } @@ -182,3 +211,4 @@ func jsonUnescapeString(s string) string { } return result } + diff --git a/onebot/img_builder.go b/onebot/img_builder.go index d2b8f0f..d0d2845 100644 --- a/onebot/img_builder.go +++ b/onebot/img_builder.go @@ -1,36 +1,18 @@ package main import ( - "crypto/rand" "encoding/hex" "fmt" - mathrand "math/rand" "strconv" "time" - "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto" + wxproto "github.com/yincongcyincong/weixin-macos/onebot/proto" "google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/proto" ) // BuildImgMsgProto 构建发送图片消息的protobuf并返回hex编码的字符串 func BuildImgMsgProto(sender, targetId, cdnKey, aesKey, md5Key string) (string, error) { - // 生成16字节随机client_proof (模拟JS的generateBytes(16)) - clientProof := make([]byte, 16) - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - randBytes := make([]byte, 16) - rand.Read(randBytes) - for i := range clientProof { - clientProof[i] = chars[int(randBytes[i])%len(chars)] - } - - // 生成5字节varint范围的msgId (2^28 ~ 2^35-1) - msgId := mathrand.Int63n(1<<35-1<<28) + (1 << 28) - - // header.unknown4 的值: 从 [0xAF, 0xAC, 0x90, 0x93, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01] 解码 - // = -228321745 (signed int64) - const headerUnknown4 int64 = -228321745 - // client_msg_id: targetId_timestamp_160_xwechat_3 timestamp := time.Now().Unix() clientMsgId := targetId + "_" + strconv.FormatInt(timestamp, 10) + "_160_xwechat_3" @@ -41,11 +23,11 @@ func BuildImgMsgProto(sender, targetId, cdnKey, aesKey, md5Key string) (string, msg := &wxproto.WxSendImgMsg{ Header: &wxproto.ImgMsgHeader{ Flag: []byte{0x00}, - MsgId: msgId, - ClientProof: clientProof, - Unknown4: headerUnknown4, - SysInfo: []byte("UnifiedPCMac 26 arm64"), - Unknown6: 304, + SessionId: int64(globalSessionId), + ClientProof: globalClientProof, + DeviceId: int64(globalDeviceId), + Platform: []byte("UnifiedPCMac 26 arm64"), + Version: 304, }, ClientMsgId: &wxproto.WxString{Value: clientMsgId}, Sender: &wxproto.WxString{Value: sender}, diff --git a/onebot/main.go b/onebot/main.go index 5187d31..2a91302 100644 --- a/onebot/main.go +++ b/onebot/main.go @@ -27,6 +27,7 @@ func main() { initFrida() } go SendWorker() + go cleanExpiredDownloads() http.HandleFunc("/send_private_msg", sendHandler) http.HandleFunc("/send_group_msg", sendHandler) @@ -60,7 +61,7 @@ func initFlag() { flag.StringVar(&config.FridaGadgetAddr, "gadget_addr", "127.0.0.1:27042", "Gadget 地址: 127.0.0.1:27042 仅当 type 为 gadget 时有效") flag.StringVar(&config.OnebotToken, "token", "MuseBot", "OneBot Token: MuseBot") flag.StringVar(&config.ImagePath, "image_path", "", "图片路径: /Users/xxx/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/xxx/temp/xxx/2026-01/Img/") - flag.StringVar(&config.WechatConf, "wechat_conf", "../wechat_version/4_1_9_52_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json") + flag.StringVar(&config.WechatConf, "wechat_conf", "../wechat_version/4_1_9_58_mac.json", "微信配置文件路径: ../wechat_version/4_1_6_12_mac.json") flag.StringVar(&config.ConnType, "conn_type", "http", "连接类型: http | websocket") flag.IntVar(&config.SendInterval, "send_interval", 1000, "发送间隔: ms") flag.IntVar(&config.WechatPid, "wechat_pid", 0, "微信进程 PID,不设置则自动查找") @@ -125,8 +126,8 @@ func attachWechat() { if err == nil { break } - Info("未发现正在运行的微信进程,5秒后重试...") - time.Sleep(5 * time.Second) + Info("未发现正在运行的微信进程,20秒后重试...") + time.Sleep(20 * time.Second) } Info("自动发现微信进程 PID", "PID", pid) } @@ -207,22 +208,35 @@ func loadJs() { } else { go SendWebSocketMsg(payloadJson) } - case "finish": - finishChan <- struct{}{} - case "upload": - if selfId, ok := pMap["self_id"]; ok && myWechatId == "" { - myWechatId = selfId.(string) - } + case "buf2resp": + go func() { + msgType := "" + if mt, ok := pMap["msg_type"]; ok { + msgType = mt.(string) + } + if dataInter, ok := pMap["data"]; ok { + if dataArr, ok := dataInter.([]interface{}); ok { + rawBytes := make([]byte, len(dataArr)) + for i, v := range dataArr { + if f, ok := v.(float64); ok { + rawBytes[i] = byte(int(f)) + } + } + HandleBuf2Resp(msgType, rawBytes) + } + } + }() case "upload_image_finish": m := &SendMsg{ Type: "send_image", } + targetId := "" if targetIdInter, ok := pMap["target_id"]; ok { - targetIdStr := targetIdInter.(string) - if strings.Contains(targetIdStr, "wxid_") { - m.UserId = targetIdStr + targetId = targetIdInter.(string) + if strings.Contains(targetId, "wxid_") { + m.UserId = targetId } else { - m.GroupID = targetIdStr + m.GroupID = targetId } } if cdnKey, ok := pMap["cdn_key"]; ok { @@ -234,17 +248,21 @@ func loadJs() { if md5Key, ok := pMap["md5_key"]; ok { m.Md5Key = md5Key.(string) } + if ch, ok := pendingResultMap.LoadAndDelete(targetId); ok { + m.ResultChan = ch.(chan error) + } msgChan <- m case "upload_video_finish": m := &SendMsg{ Type: "send_video", } + targetId := "" if targetIdInter, ok := pMap["target_id"]; ok { - targetIdStr := targetIdInter.(string) - if strings.Contains(targetIdStr, "wxid_") { - m.UserId = targetIdStr + targetId = targetIdInter.(string) + if strings.Contains(targetId, "wxid_") { + m.UserId = targetId } else { - m.GroupID = targetIdStr + m.GroupID = targetId } } if cdnKey, ok := pMap["cdn_key"]; ok { @@ -259,6 +277,9 @@ func loadJs() { if videoId, ok := pMap["video_id"]; ok { m.VideoId = videoId.(string) } + if ch, ok := pendingResultMap.LoadAndDelete(targetId); ok { + m.ResultChan = ch.(chan error) + } msgChan <- m case "download": err = Download(payloadJson) diff --git a/onebot/msg.go b/onebot/msg.go index be3043a..92da9f7 100644 --- a/onebot/msg.go +++ b/onebot/msg.go @@ -5,6 +5,20 @@ import ( "time" ) +func cleanExpiredDownloads() { + ticker := time.NewTicker(1 * time.Hour) + for range ticker.C { + now := time.Now().UnixMilli() + userID2FileMsgMap.Range(func(key, value any) bool { + req := value.(*DownloadRequest) + if now-req.LastAppendTime > 24*60*60*1000 { + userID2FileMsgMap.Delete(key) + } + return true + }) + } +} + func Download(rawMsg []byte) error { downloadReq := new(DownloadRequest) err := json.Unmarshal(rawMsg, downloadReq) diff --git a/onebot/param.go b/onebot/param.go index bcf422a..1aa68f8 100644 --- a/onebot/param.go +++ b/onebot/param.go @@ -2,8 +2,10 @@ package main import ( "encoding/xml" + "fmt" + "math/rand" "sync" - + "github.com/frida/frida-go/frida" ) @@ -14,15 +16,21 @@ var ( device frida.DeviceInt taskId = int64(0x20000000) myWechatId = "" - - msgChan = make(chan *SendMsg, 100) - finishChan = make(chan struct{}, 100) - + + // 全局共享的header字段,启动时初始化一次,两个protobuf共用 + globalSessionId = uint32(rand.Int63n(4000000000) + 100000000) + globalDeviceId = rand.Uint64() | (0xFFFFFFFF << 32) + globalClientProof = []byte(fmt.Sprintf("m64%s", generateClientProof(13))) + + msgChan = make(chan *SendMsg, 100) + pendingResultMap sync.Map // targetId -> chan error + config = &Config{} - - userID2NicknameMap sync.Map - userID2FileMsgMap sync.Map - videoInfoMap sync.Map // targetId -> *VideoInfo + + userID2NicknameMap sync.Map + userID2FileMsgMap sync.Map + videoInfoMap sync.Map // targetId -> *VideoInfo + buf2RespChan = make(chan *Buf2RespData, 10) ) type WechatMessage struct { @@ -58,9 +66,10 @@ type SendMsg struct { FilePath string FileType int - CdnKey string - Md5Key string - VideoId string + CdnKey string + Md5Key string + VideoId string + Duration int32 VideoSize int32 @@ -71,6 +80,8 @@ type SendMsg struct { ReferMsgsource string ReferDisplayName string ReferContent string + + ResultChan chan error // 发送结果回传 } type VideoInfo struct { @@ -78,6 +89,12 @@ type VideoInfo struct { VideoSize int32 } +type Buf2RespData struct { + MsgType string + Data []byte + Err error +} + // SendRequest 请求结构体 type SendRequest struct { Message []*Message `json:"message"` @@ -91,13 +108,13 @@ type Message struct { } type SendRequestData struct { - Id string `json:"id,omitempty"` - Text string `json:"text,omitempty"` - File string `json:"file,omitempty"` - URL string `json:"url,omitempty"` - QQ string `json:"qq,omitempty"` - Media []byte `json:"media,omitempty"` - ReplyMessage *WechatMessage `json:"reply_message,omitempty"` + Id string `json:"id,omitempty"` + Text string `json:"text,omitempty"` + File string `json:"file,omitempty"` + URL string `json:"url,omitempty"` + QQ string `json:"qq,omitempty"` + Media []byte `json:"media,omitempty"` + ReplyMessage *WechatMessage `json:"reply_message,omitempty"` } type Config struct { @@ -163,7 +180,7 @@ type Image struct { HDHeight int `xml:"cdnhdheight,attr"` HDWidth int `xml:"cdnhdwidth,attr"` MidImgURL string `xml:"cdnmidimgurl,attr"` - + // 子节点 SecHashInfo string `xml:"secHashInfoBase64"` Live Live `xml:"live"` diff --git a/onebot/proto/wxproto/reply_msg.pb.go b/onebot/proto/app_msg.pb.go similarity index 76% rename from onebot/proto/wxproto/reply_msg.pb.go rename to onebot/proto/app_msg.pb.go index c19afdb..c6c5854 100644 --- a/onebot/proto/wxproto/reply_msg.pb.go +++ b/onebot/proto/app_msg.pb.go @@ -2,7 +2,7 @@ // versions: // protoc-gen-go v1.36.11 // protoc v3.5.1 -// source: proto/wxproto/reply_msg.proto +// source: app_msg.proto package wxproto @@ -28,13 +28,17 @@ type WxSendReplyMsg struct { state protoimpl.MessageState `protogen:"open.v1"` Header *ReplyMsgHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"` Body *ReplyMsgBody `protobuf:"bytes,2,opt,name=body" json:"body,omitempty"` + Unknown5 []byte `protobuf:"bytes,5,opt,name=unknown5" json:"unknown5,omitempty"` // md5 + Unknown9 *int32 `protobuf:"varint,9,opt,name=unknown9" json:"unknown9,omitempty"` // 1 + Unknown10 *uint64 `protobuf:"varint,10,opt,name=unknown10" json:"unknown10,omitempty"` // msgSvrId + Unknown11 *int32 `protobuf:"varint,11,opt,name=unknown11" json:"unknown11,omitempty"` // 2 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *WxSendReplyMsg) Reset() { *x = WxSendReplyMsg{} - mi := &file_proto_wxproto_reply_msg_proto_msgTypes[0] + mi := &file_app_msg_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -46,7 +50,7 @@ func (x *WxSendReplyMsg) String() string { func (*WxSendReplyMsg) ProtoMessage() {} func (x *WxSendReplyMsg) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_reply_msg_proto_msgTypes[0] + mi := &file_app_msg_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -59,7 +63,7 @@ func (x *WxSendReplyMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use WxSendReplyMsg.ProtoReflect.Descriptor instead. func (*WxSendReplyMsg) Descriptor() ([]byte, []int) { - return file_proto_wxproto_reply_msg_proto_rawDescGZIP(), []int{0} + return file_app_msg_proto_rawDescGZIP(), []int{0} } func (x *WxSendReplyMsg) GetHeader() *ReplyMsgHeader { @@ -76,22 +80,50 @@ func (x *WxSendReplyMsg) GetBody() *ReplyMsgBody { return nil } -// Header: 固定64字节 +func (x *WxSendReplyMsg) GetUnknown5() []byte { + if x != nil { + return x.Unknown5 + } + return nil +} + +func (x *WxSendReplyMsg) GetUnknown9() int32 { + if x != nil && x.Unknown9 != nil { + return *x.Unknown9 + } + return 0 +} + +func (x *WxSendReplyMsg) GetUnknown10() uint64 { + if x != nil && x.Unknown10 != nil { + return *x.Unknown10 + } + return 0 +} + +func (x *WxSendReplyMsg) GetUnknown11() int32 { + if x != nil && x.Unknown11 != nil { + return *x.Unknown11 + } + return 0 +} + +// Header: 请求头,所有消息共用 type ReplyMsgHeader struct { state protoimpl.MessageState `protogen:"open.v1"` Flag []byte `protobuf:"bytes,1,opt,name=flag" json:"flag,omitempty"` // 固定 [0x00] - Timestamp *uint32 `protobuf:"varint,2,opt,name=timestamp" json:"timestamp,omitempty"` // 请求时间戳(秒) - ClientProof []byte `protobuf:"bytes,3,opt,name=client_proof,json=clientProof" json:"client_proof,omitempty"` // 16字节随机数据/md5 - DeviceId *uint64 `protobuf:"varint,4,opt,name=device_id,json=deviceId" json:"device_id,omitempty"` // 设备标识(大整数) + SessionId *uint32 `protobuf:"varint,2,opt,name=session_id,json=sessionId" json:"session_id,omitempty"` // 会话ID (启动时生成,全局复用) + ClientProof []byte `protobuf:"bytes,3,opt,name=client_proof,json=clientProof" json:"client_proof,omitempty"` // 16字节设备标识字符串 (如"m64a7624402b3883") + DeviceId *uint64 `protobuf:"varint,4,opt,name=device_id,json=deviceId" json:"device_id,omitempty"` // 设备ID (大整数,启动时生成,全局复用) Platform *string `protobuf:"bytes,5,opt,name=platform" json:"platform,omitempty"` // 平台信息 "UnifiedPCMac 26 arm64" - Version *uint32 `protobuf:"varint,6,opt,name=version" json:"version,omitempty"` // 版本/标记 = 163 + Version *uint32 `protobuf:"varint,6,opt,name=version" json:"version,omitempty"` // 版本标记 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ReplyMsgHeader) Reset() { *x = ReplyMsgHeader{} - mi := &file_proto_wxproto_reply_msg_proto_msgTypes[1] + mi := &file_app_msg_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -103,7 +135,7 @@ func (x *ReplyMsgHeader) String() string { func (*ReplyMsgHeader) ProtoMessage() {} func (x *ReplyMsgHeader) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_reply_msg_proto_msgTypes[1] + mi := &file_app_msg_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -116,7 +148,7 @@ func (x *ReplyMsgHeader) ProtoReflect() protoreflect.Message { // Deprecated: Use ReplyMsgHeader.ProtoReflect.Descriptor instead. func (*ReplyMsgHeader) Descriptor() ([]byte, []int) { - return file_proto_wxproto_reply_msg_proto_rawDescGZIP(), []int{1} + return file_app_msg_proto_rawDescGZIP(), []int{1} } func (x *ReplyMsgHeader) GetFlag() []byte { @@ -126,9 +158,9 @@ func (x *ReplyMsgHeader) GetFlag() []byte { return nil } -func (x *ReplyMsgHeader) GetTimestamp() uint32 { - if x != nil && x.Timestamp != nil { - return *x.Timestamp +func (x *ReplyMsgHeader) GetSessionId() uint32 { + if x != nil && x.SessionId != nil { + return *x.SessionId } return 0 } @@ -185,7 +217,7 @@ type ReplyMsgBody struct { func (x *ReplyMsgBody) Reset() { *x = ReplyMsgBody{} - mi := &file_proto_wxproto_reply_msg_proto_msgTypes[2] + mi := &file_app_msg_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -197,7 +229,7 @@ func (x *ReplyMsgBody) String() string { func (*ReplyMsgBody) ProtoMessage() {} func (x *ReplyMsgBody) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_reply_msg_proto_msgTypes[2] + mi := &file_app_msg_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -210,7 +242,7 @@ func (x *ReplyMsgBody) ProtoReflect() protoreflect.Message { // Deprecated: Use ReplyMsgBody.ProtoReflect.Descriptor instead. func (*ReplyMsgBody) Descriptor() ([]byte, []int) { - return file_proto_wxproto_reply_msg_proto_rawDescGZIP(), []int{2} + return file_app_msg_proto_rawDescGZIP(), []int{2} } func (x *ReplyMsgBody) GetSender() string { @@ -318,17 +350,23 @@ func (x *ReplyMsgBody) GetUnknown15() []byte { return nil } -var File_proto_wxproto_reply_msg_proto protoreflect.FileDescriptor +var File_app_msg_proto protoreflect.FileDescriptor -const file_proto_wxproto_reply_msg_proto_rawDesc = "" + +const file_app_msg_proto_rawDesc = "" + "\n" + - "\x1dproto/wxproto/reply_msg.proto\x12\awxproto\"l\n" + + "\rapp_msg.proto\x12\awxproto\"\xe0\x01\n" + "\x0eWxSendReplyMsg\x12/\n" + "\x06header\x18\x01 \x01(\v2\x17.wxproto.ReplyMsgHeaderR\x06header\x12)\n" + - "\x04body\x18\x02 \x01(\v2\x15.wxproto.ReplyMsgBodyR\x04body\"\xb8\x01\n" + + "\x04body\x18\x02 \x01(\v2\x15.wxproto.ReplyMsgBodyR\x04body\x12\x1a\n" + + "\bunknown5\x18\x05 \x01(\fR\bunknown5\x12\x1a\n" + + "\bunknown9\x18\t \x01(\x05R\bunknown9\x12\x1c\n" + + "\tunknown10\x18\n" + + " \x01(\x04R\tunknown10\x12\x1c\n" + + "\tunknown11\x18\v \x01(\x05R\tunknown11\"\xb9\x01\n" + "\x0eReplyMsgHeader\x12\x12\n" + - "\x04flag\x18\x01 \x01(\fR\x04flag\x12\x1c\n" + - "\ttimestamp\x18\x02 \x01(\rR\ttimestamp\x12!\n" + + "\x04flag\x18\x01 \x01(\fR\x04flag\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\rR\tsessionId\x12!\n" + "\fclient_proof\x18\x03 \x01(\fR\vclientProof\x12\x1b\n" + "\tdevice_id\x18\x04 \x01(\x04R\bdeviceId\x12\x1a\n" + "\bplatform\x18\x05 \x01(\tR\bplatform\x12\x18\n" + @@ -349,27 +387,27 @@ const file_proto_wxproto_reply_msg_proto_rawDesc = "" + "\tmsgsource\x18\f \x01(\fR\tmsgsource\x12\x1c\n" + "\tunknown13\x18\r \x01(\fR\tunknown13\x12\x1c\n" + "\tunknown14\x18\x0e \x01(\fR\tunknown14\x12\x1c\n" + - "\tunknown15\x18\x0f \x01(\fR\tunknown15B>ZZ wxproto.ReplyMsgHeader 2, // 1: wxproto.WxSendReplyMsg.body:type_name -> wxproto.ReplyMsgBody 2, // [2:2] is the sub-list for method output_type @@ -379,26 +417,26 @@ var file_proto_wxproto_reply_msg_proto_depIdxs = []int32{ 0, // [0:2] is the sub-list for field type_name } -func init() { file_proto_wxproto_reply_msg_proto_init() } -func file_proto_wxproto_reply_msg_proto_init() { - if File_proto_wxproto_reply_msg_proto != nil { +func init() { file_app_msg_proto_init() } +func file_app_msg_proto_init() { + if File_app_msg_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_reply_msg_proto_rawDesc), len(file_proto_wxproto_reply_msg_proto_rawDesc)), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_msg_proto_rawDesc), len(file_app_msg_proto_rawDesc)), NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_proto_wxproto_reply_msg_proto_goTypes, - DependencyIndexes: file_proto_wxproto_reply_msg_proto_depIdxs, - MessageInfos: file_proto_wxproto_reply_msg_proto_msgTypes, + GoTypes: file_app_msg_proto_goTypes, + DependencyIndexes: file_app_msg_proto_depIdxs, + MessageInfos: file_app_msg_proto_msgTypes, }.Build() - File_proto_wxproto_reply_msg_proto = out.File - file_proto_wxproto_reply_msg_proto_goTypes = nil - file_proto_wxproto_reply_msg_proto_depIdxs = nil + File_app_msg_proto = out.File + file_app_msg_proto_goTypes = nil + file_app_msg_proto_depIdxs = nil } diff --git a/onebot/proto/wxproto/reply_msg.proto b/onebot/proto/app_msg.proto similarity index 71% rename from onebot/proto/wxproto/reply_msg.proto rename to onebot/proto/app_msg.proto index 957c1d1..e435957 100644 --- a/onebot/proto/wxproto/reply_msg.proto +++ b/onebot/proto/app_msg.proto @@ -2,7 +2,7 @@ syntax = "proto2"; package wxproto; -option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"; +option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto;wxproto"; // 发送回复(引用)消息的protobuf结构 // CG路径: /cgi-bin/micromsg-bin/sendappmsg @@ -10,16 +10,20 @@ option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxprot message WxSendReplyMsg { optional ReplyMsgHeader header = 1; optional ReplyMsgBody body = 2; + optional bytes unknown5 = 5; // md5 + optional int32 unknown9 = 9; // 1 + optional uint64 unknown10 = 10; // msgSvrId + optional int32 unknown11 = 11; // 2 } -// Header: 固定64字节 +// Header: 请求头,所有消息共用 message ReplyMsgHeader { optional bytes flag = 1; // 固定 [0x00] - optional uint32 timestamp = 2; // 请求时间戳(秒) - optional bytes client_proof = 3; // 16字节随机数据/md5 - optional uint64 device_id = 4; // 设备标识(大整数) + optional uint32 session_id = 2; // 会话ID (启动时生成,全局复用) + optional bytes client_proof = 3; // 16字节设备标识字符串 (如"m64a7624402b3883") + optional uint64 device_id = 4; // 设备ID (大整数,启动时生成,全局复用) optional string platform = 5; // 平台信息 "UnifiedPCMac 26 arm64" - optional uint32 version = 6; // 版本/标记 = 163 + optional uint32 version = 6; // 版本标记 } // Body: 消息体 diff --git a/onebot/proto/common_resp.pb.go b/onebot/proto/common_resp.pb.go new file mode 100644 index 0000000..dcb132e --- /dev/null +++ b/onebot/proto/common_resp.pb.go @@ -0,0 +1,456 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.5.1 +// source: common_resp.proto + +package wxproto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// 通用响应: BaseResponse 在 field 1 (适用于 text/video/image/file/reply 等) +type SendMsgResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + BaseResponse *BaseResponse `protobuf:"bytes,1,opt,name=baseResponse" json:"baseResponse,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendMsgResponse) Reset() { + *x = SendMsgResponse{} + mi := &file_common_resp_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendMsgResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendMsgResponse) ProtoMessage() {} + +func (x *SendMsgResponse) ProtoReflect() protoreflect.Message { + mi := &file_common_resp_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendMsgResponse.ProtoReflect.Descriptor instead. +func (*SendMsgResponse) Descriptor() ([]byte, []int) { + return file_common_resp_proto_rawDescGZIP(), []int{0} +} + +func (x *SendMsgResponse) GetBaseResponse() *BaseResponse { + if x != nil { + return x.BaseResponse + } + return nil +} + +// 语音响应: BaseResponse 在 field 10 +type SendVoiceMsgResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + SenderWxid *string `protobuf:"bytes,1,opt,name=senderWxid" json:"senderWxid,omitempty"` + ReceiverWxid *string `protobuf:"bytes,2,opt,name=receiverWxid" json:"receiverWxid,omitempty"` + Field3 *uint64 `protobuf:"varint,3,opt,name=field3" json:"field3,omitempty"` + Field4 *uint64 `protobuf:"varint,4,opt,name=field4" json:"field4,omitempty"` + Field5 *uint64 `protobuf:"varint,5,opt,name=field5" json:"field5,omitempty"` + ClientMsgId *string `protobuf:"bytes,6,opt,name=clientMsgId" json:"clientMsgId,omitempty"` + NewMsgId *uint64 `protobuf:"varint,7,opt,name=newMsgId" json:"newMsgId,omitempty"` + Field8 *uint64 `protobuf:"varint,8,opt,name=field8" json:"field8,omitempty"` + Field9 *uint64 `protobuf:"varint,9,opt,name=field9" json:"field9,omitempty"` + BaseResponse *BaseResponse `protobuf:"bytes,10,opt,name=baseResponse" json:"baseResponse,omitempty"` + Field11 *uint64 `protobuf:"varint,11,opt,name=field11" json:"field11,omitempty"` + MsgSvrId *uint64 `protobuf:"varint,12,opt,name=msgSvrId" json:"msgSvrId,omitempty"` + Field13 *uint64 `protobuf:"varint,13,opt,name=field13" json:"field13,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendVoiceMsgResponse) Reset() { + *x = SendVoiceMsgResponse{} + mi := &file_common_resp_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendVoiceMsgResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendVoiceMsgResponse) ProtoMessage() {} + +func (x *SendVoiceMsgResponse) ProtoReflect() protoreflect.Message { + mi := &file_common_resp_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendVoiceMsgResponse.ProtoReflect.Descriptor instead. +func (*SendVoiceMsgResponse) Descriptor() ([]byte, []int) { + return file_common_resp_proto_rawDescGZIP(), []int{1} +} + +func (x *SendVoiceMsgResponse) GetSenderWxid() string { + if x != nil && x.SenderWxid != nil { + return *x.SenderWxid + } + return "" +} + +func (x *SendVoiceMsgResponse) GetReceiverWxid() string { + if x != nil && x.ReceiverWxid != nil { + return *x.ReceiverWxid + } + return "" +} + +func (x *SendVoiceMsgResponse) GetField3() uint64 { + if x != nil && x.Field3 != nil { + return *x.Field3 + } + return 0 +} + +func (x *SendVoiceMsgResponse) GetField4() uint64 { + if x != nil && x.Field4 != nil { + return *x.Field4 + } + return 0 +} + +func (x *SendVoiceMsgResponse) GetField5() uint64 { + if x != nil && x.Field5 != nil { + return *x.Field5 + } + return 0 +} + +func (x *SendVoiceMsgResponse) GetClientMsgId() string { + if x != nil && x.ClientMsgId != nil { + return *x.ClientMsgId + } + return "" +} + +func (x *SendVoiceMsgResponse) GetNewMsgId() uint64 { + if x != nil && x.NewMsgId != nil { + return *x.NewMsgId + } + return 0 +} + +func (x *SendVoiceMsgResponse) GetField8() uint64 { + if x != nil && x.Field8 != nil { + return *x.Field8 + } + return 0 +} + +func (x *SendVoiceMsgResponse) GetField9() uint64 { + if x != nil && x.Field9 != nil { + return *x.Field9 + } + return 0 +} + +func (x *SendVoiceMsgResponse) GetBaseResponse() *BaseResponse { + if x != nil { + return x.BaseResponse + } + return nil +} + +func (x *SendVoiceMsgResponse) GetField11() uint64 { + if x != nil && x.Field11 != nil { + return *x.Field11 + } + return 0 +} + +func (x *SendVoiceMsgResponse) GetMsgSvrId() uint64 { + if x != nil && x.MsgSvrId != nil { + return *x.MsgSvrId + } + return 0 +} + +func (x *SendVoiceMsgResponse) GetField13() uint64 { + if x != nil && x.Field13 != nil { + return *x.Field13 + } + return 0 +} + +// sendfileuploadmsg 专用响应 +type SendFileUploadMsgResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + BaseResponse *BaseResponse `protobuf:"bytes,1,opt,name=baseResponse" json:"baseResponse,omitempty"` + FileUploadToken *string `protobuf:"bytes,2,opt,name=fileUploadToken" json:"fileUploadToken,omitempty"` + MsgSvrId *uint64 `protobuf:"varint,3,opt,name=msgSvrId" json:"msgSvrId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendFileUploadMsgResponse) Reset() { + *x = SendFileUploadMsgResponse{} + mi := &file_common_resp_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendFileUploadMsgResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendFileUploadMsgResponse) ProtoMessage() {} + +func (x *SendFileUploadMsgResponse) ProtoReflect() protoreflect.Message { + mi := &file_common_resp_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendFileUploadMsgResponse.ProtoReflect.Descriptor instead. +func (*SendFileUploadMsgResponse) Descriptor() ([]byte, []int) { + return file_common_resp_proto_rawDescGZIP(), []int{2} +} + +func (x *SendFileUploadMsgResponse) GetBaseResponse() *BaseResponse { + if x != nil { + return x.BaseResponse + } + return nil +} + +func (x *SendFileUploadMsgResponse) GetFileUploadToken() string { + if x != nil && x.FileUploadToken != nil { + return *x.FileUploadToken + } + return "" +} + +func (x *SendFileUploadMsgResponse) GetMsgSvrId() uint64 { + if x != nil && x.MsgSvrId != nil { + return *x.MsgSvrId + } + return 0 +} + +type BaseResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ret *int32 `protobuf:"varint,1,opt,name=ret" json:"ret,omitempty"` + ErrMsg *BaseErrMsg `protobuf:"bytes,2,opt,name=errMsg" json:"errMsg,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BaseResponse) Reset() { + *x = BaseResponse{} + mi := &file_common_resp_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BaseResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BaseResponse) ProtoMessage() {} + +func (x *BaseResponse) ProtoReflect() protoreflect.Message { + mi := &file_common_resp_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BaseResponse.ProtoReflect.Descriptor instead. +func (*BaseResponse) Descriptor() ([]byte, []int) { + return file_common_resp_proto_rawDescGZIP(), []int{3} +} + +func (x *BaseResponse) GetRet() int32 { + if x != nil && x.Ret != nil { + return *x.Ret + } + return 0 +} + +func (x *BaseResponse) GetErrMsg() *BaseErrMsg { + if x != nil { + return x.ErrMsg + } + return nil +} + +type BaseErrMsg struct { + state protoimpl.MessageState `protogen:"open.v1"` + Msg *string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BaseErrMsg) Reset() { + *x = BaseErrMsg{} + mi := &file_common_resp_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BaseErrMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BaseErrMsg) ProtoMessage() {} + +func (x *BaseErrMsg) ProtoReflect() protoreflect.Message { + mi := &file_common_resp_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BaseErrMsg.ProtoReflect.Descriptor instead. +func (*BaseErrMsg) Descriptor() ([]byte, []int) { + return file_common_resp_proto_rawDescGZIP(), []int{4} +} + +func (x *BaseErrMsg) GetMsg() string { + if x != nil && x.Msg != nil { + return *x.Msg + } + return "" +} + +var File_common_resp_proto protoreflect.FileDescriptor + +const file_common_resp_proto_rawDesc = "" + + "\n" + + "\x11common_resp.proto\x12\awxproto\"L\n" + + "\x0fSendMsgResponse\x129\n" + + "\fbaseResponse\x18\x01 \x01(\v2\x15.wxproto.BaseResponseR\fbaseResponse\"\x9b\x03\n" + + "\x14SendVoiceMsgResponse\x12\x1e\n" + + "\n" + + "senderWxid\x18\x01 \x01(\tR\n" + + "senderWxid\x12\"\n" + + "\freceiverWxid\x18\x02 \x01(\tR\freceiverWxid\x12\x16\n" + + "\x06field3\x18\x03 \x01(\x04R\x06field3\x12\x16\n" + + "\x06field4\x18\x04 \x01(\x04R\x06field4\x12\x16\n" + + "\x06field5\x18\x05 \x01(\x04R\x06field5\x12 \n" + + "\vclientMsgId\x18\x06 \x01(\tR\vclientMsgId\x12\x1a\n" + + "\bnewMsgId\x18\a \x01(\x04R\bnewMsgId\x12\x16\n" + + "\x06field8\x18\b \x01(\x04R\x06field8\x12\x16\n" + + "\x06field9\x18\t \x01(\x04R\x06field9\x129\n" + + "\fbaseResponse\x18\n" + + " \x01(\v2\x15.wxproto.BaseResponseR\fbaseResponse\x12\x18\n" + + "\afield11\x18\v \x01(\x04R\afield11\x12\x1a\n" + + "\bmsgSvrId\x18\f \x01(\x04R\bmsgSvrId\x12\x18\n" + + "\afield13\x18\r \x01(\x04R\afield13\"\x9c\x01\n" + + "\x19SendFileUploadMsgResponse\x129\n" + + "\fbaseResponse\x18\x01 \x01(\v2\x15.wxproto.BaseResponseR\fbaseResponse\x12(\n" + + "\x0ffileUploadToken\x18\x02 \x01(\tR\x0ffileUploadToken\x12\x1a\n" + + "\bmsgSvrId\x18\x03 \x01(\x04R\bmsgSvrId\"M\n" + + "\fBaseResponse\x12\x10\n" + + "\x03ret\x18\x01 \x01(\x05R\x03ret\x12+\n" + + "\x06errMsg\x18\x02 \x01(\v2\x13.wxproto.BaseErrMsgR\x06errMsg\"\x1e\n" + + "\n" + + "BaseErrMsg\x12\x10\n" + + "\x03msg\x18\x01 \x01(\tR\x03msgB>Z wxproto.BaseResponse + 3, // 1: wxproto.SendVoiceMsgResponse.baseResponse:type_name -> wxproto.BaseResponse + 3, // 2: wxproto.SendFileUploadMsgResponse.baseResponse:type_name -> wxproto.BaseResponse + 4, // 3: wxproto.BaseResponse.errMsg:type_name -> wxproto.BaseErrMsg + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_common_resp_proto_init() } +func file_common_resp_proto_init() { + if File_common_resp_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_resp_proto_rawDesc), len(file_common_resp_proto_rawDesc)), + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_common_resp_proto_goTypes, + DependencyIndexes: file_common_resp_proto_depIdxs, + MessageInfos: file_common_resp_proto_msgTypes, + }.Build() + File_common_resp_proto = out.File + file_common_resp_proto_goTypes = nil + file_common_resp_proto_depIdxs = nil +} diff --git a/onebot/proto/common_resp.proto b/onebot/proto/common_resp.proto new file mode 100644 index 0000000..1e0295b --- /dev/null +++ b/onebot/proto/common_resp.proto @@ -0,0 +1,43 @@ +syntax = "proto2"; + +package wxproto; + +option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto;wxproto"; + +// 通用响应: BaseResponse 在 field 1 (适用于 text/video/image/file/reply 等) +message SendMsgResponse { + optional BaseResponse baseResponse = 1; +} + +// 语音响应: BaseResponse 在 field 10 +message SendVoiceMsgResponse { + optional string senderWxid = 1; + optional string receiverWxid = 2; + optional uint64 field3 = 3; + optional uint64 field4 = 4; + optional uint64 field5 = 5; + optional string clientMsgId = 6; + optional uint64 newMsgId = 7; + optional uint64 field8 = 8; + optional uint64 field9 = 9; + optional BaseResponse baseResponse = 10; + optional uint64 field11 = 11; + optional uint64 msgSvrId = 12; + optional uint64 field13 = 13; +} + +// sendfileuploadmsg 专用响应 +message SendFileUploadMsgResponse { + optional BaseResponse baseResponse = 1; + optional string fileUploadToken = 2; + optional uint64 msgSvrId = 3; +} + +message BaseResponse { + optional int32 ret = 1; + optional BaseErrMsg errMsg = 2; +} + +message BaseErrMsg { + optional string msg = 1; +} diff --git a/onebot/proto/wxproto/img_msg.pb.go b/onebot/proto/img_msg.pb.go similarity index 79% rename from onebot/proto/wxproto/img_msg.pb.go rename to onebot/proto/img_msg.pb.go index f0d4cdf..cc0c4c6 100644 --- a/onebot/proto/wxproto/img_msg.pb.go +++ b/onebot/proto/img_msg.pb.go @@ -2,7 +2,7 @@ // versions: // protoc-gen-go v1.36.11 // protoc v3.5.1 -// source: proto/wxproto/img_msg.proto +// source: img_msg.proto package wxproto @@ -64,7 +64,7 @@ type WxSendImgMsg struct { func (x *WxSendImgMsg) Reset() { *x = WxSendImgMsg{} - mi := &file_proto_wxproto_img_msg_proto_msgTypes[0] + mi := &file_img_msg_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -76,7 +76,7 @@ func (x *WxSendImgMsg) String() string { func (*WxSendImgMsg) ProtoMessage() {} func (x *WxSendImgMsg) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_img_msg_proto_msgTypes[0] + mi := &file_img_msg_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -89,7 +89,7 @@ func (x *WxSendImgMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use WxSendImgMsg.ProtoReflect.Descriptor instead. func (*WxSendImgMsg) Descriptor() ([]byte, []int) { - return file_proto_wxproto_img_msg_proto_rawDescGZIP(), []int{0} + return file_img_msg_proto_rawDescGZIP(), []int{0} } func (x *WxSendImgMsg) GetHeader() *ImgMsgHeader { @@ -298,18 +298,18 @@ func (x *WxSendImgMsg) GetUnknown41() int32 { type ImgMsgHeader struct { state protoimpl.MessageState `protogen:"open.v1"` Flag []byte `protobuf:"bytes,1,opt,name=flag,proto3" json:"flag,omitempty"` - MsgId int64 `protobuf:"varint,2,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"` + SessionId int64 `protobuf:"varint,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` ClientProof []byte `protobuf:"bytes,3,opt,name=client_proof,json=clientProof,proto3" json:"client_proof,omitempty"` - Unknown4 int64 `protobuf:"varint,4,opt,name=unknown4,proto3" json:"unknown4,omitempty"` - SysInfo []byte `protobuf:"bytes,5,opt,name=sys_info,json=sysInfo,proto3" json:"sys_info,omitempty"` - Unknown6 int32 `protobuf:"varint,6,opt,name=unknown6,proto3" json:"unknown6,omitempty"` + DeviceId int64 `protobuf:"varint,4,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + Platform []byte `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` + Version int32 `protobuf:"varint,6,opt,name=version,proto3" json:"version,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ImgMsgHeader) Reset() { *x = ImgMsgHeader{} - mi := &file_proto_wxproto_img_msg_proto_msgTypes[1] + mi := &file_img_msg_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -321,7 +321,7 @@ func (x *ImgMsgHeader) String() string { func (*ImgMsgHeader) ProtoMessage() {} func (x *ImgMsgHeader) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_img_msg_proto_msgTypes[1] + mi := &file_img_msg_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -334,7 +334,7 @@ func (x *ImgMsgHeader) ProtoReflect() protoreflect.Message { // Deprecated: Use ImgMsgHeader.ProtoReflect.Descriptor instead. func (*ImgMsgHeader) Descriptor() ([]byte, []int) { - return file_proto_wxproto_img_msg_proto_rawDescGZIP(), []int{1} + return file_img_msg_proto_rawDescGZIP(), []int{1} } func (x *ImgMsgHeader) GetFlag() []byte { @@ -344,9 +344,9 @@ func (x *ImgMsgHeader) GetFlag() []byte { return nil } -func (x *ImgMsgHeader) GetMsgId() int64 { +func (x *ImgMsgHeader) GetSessionId() int64 { if x != nil { - return x.MsgId + return x.SessionId } return 0 } @@ -358,23 +358,23 @@ func (x *ImgMsgHeader) GetClientProof() []byte { return nil } -func (x *ImgMsgHeader) GetUnknown4() int64 { +func (x *ImgMsgHeader) GetDeviceId() int64 { if x != nil { - return x.Unknown4 + return x.DeviceId } return 0 } -func (x *ImgMsgHeader) GetSysInfo() []byte { +func (x *ImgMsgHeader) GetPlatform() []byte { if x != nil { - return x.SysInfo + return x.Platform } return nil } -func (x *ImgMsgHeader) GetUnknown6() int32 { +func (x *ImgMsgHeader) GetVersion() int32 { if x != nil { - return x.Unknown6 + return x.Version } return 0 } @@ -389,7 +389,7 @@ type ImgMsgExtra struct { func (x *ImgMsgExtra) Reset() { *x = ImgMsgExtra{} - mi := &file_proto_wxproto_img_msg_proto_msgTypes[2] + mi := &file_img_msg_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -401,7 +401,7 @@ func (x *ImgMsgExtra) String() string { func (*ImgMsgExtra) ProtoMessage() {} func (x *ImgMsgExtra) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_img_msg_proto_msgTypes[2] + mi := &file_img_msg_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -414,7 +414,7 @@ func (x *ImgMsgExtra) ProtoReflect() protoreflect.Message { // Deprecated: Use ImgMsgExtra.ProtoReflect.Descriptor instead. func (*ImgMsgExtra) Descriptor() ([]byte, []int) { - return file_proto_wxproto_img_msg_proto_rawDescGZIP(), []int{2} + return file_img_msg_proto_rawDescGZIP(), []int{2} } func (x *ImgMsgExtra) GetField1() int32 { @@ -431,11 +431,11 @@ func (x *ImgMsgExtra) GetField2() []byte { return nil } -var File_proto_wxproto_img_msg_proto protoreflect.FileDescriptor +var File_img_msg_proto protoreflect.FileDescriptor -const file_proto_wxproto_img_msg_proto_rawDesc = "" + +const file_img_msg_proto_rawDesc = "" + "\n" + - "\x1bproto/wxproto/img_msg.proto\x12\awxproto\x1a\x1aproto/wxproto/wx_msg.proto\"\xa3\a\n" + + "\rimg_msg.proto\x12\awxproto\x1a\fwx_msg.proto\"\xa3\a\n" + "\fWxSendImgMsg\x12-\n" + "\x06header\x18\x01 \x01(\v2\x15.wxproto.ImgMsgHeaderR\x06header\x125\n" + "\rclient_msg_id\x18\x02 \x01(\v2\x11.wxproto.WxStringR\vclientMsgId\x12)\n" + @@ -466,38 +466,39 @@ const file_proto_wxproto_img_msg_proto_rawDesc = "" + "\tunknown28\x18\x1c \x01(\x03R\tunknown28\x12\x1c\n" + "\tunknown30\x18\x1e \x01(\x05R\tunknown30\x12\x1c\n" + "\tunknown36\x18$ \x01(\x05R\tunknown36\x12\x1c\n" + - "\tunknown41\x18) \x01(\x05R\tunknown41\"\xaf\x01\n" + + "\tunknown41\x18) \x01(\x05R\tunknown41\"\xb7\x01\n" + "\fImgMsgHeader\x12\x12\n" + - "\x04flag\x18\x01 \x01(\fR\x04flag\x12\x15\n" + - "\x06msg_id\x18\x02 \x01(\x03R\x05msgId\x12!\n" + - "\fclient_proof\x18\x03 \x01(\fR\vclientProof\x12\x1a\n" + - "\bunknown4\x18\x04 \x01(\x03R\bunknown4\x12\x19\n" + - "\bsys_info\x18\x05 \x01(\fR\asysInfo\x12\x1a\n" + - "\bunknown6\x18\x06 \x01(\x05R\bunknown6\"=\n" + + "\x04flag\x18\x01 \x01(\fR\x04flag\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\x03R\tsessionId\x12!\n" + + "\fclient_proof\x18\x03 \x01(\fR\vclientProof\x12\x1b\n" + + "\tdevice_id\x18\x04 \x01(\x03R\bdeviceId\x12\x1a\n" + + "\bplatform\x18\x05 \x01(\fR\bplatform\x12\x18\n" + + "\aversion\x18\x06 \x01(\x05R\aversion\"=\n" + "\vImgMsgExtra\x12\x16\n" + "\x06field1\x18\x01 \x01(\x05R\x06field1\x12\x16\n" + - "\x06field2\x18\x02 \x01(\fR\x06field2B>ZZ wxproto.ImgMsgHeader 3, // 1: wxproto.WxSendImgMsg.client_msg_id:type_name -> wxproto.WxString 3, // 2: wxproto.WxSendImgMsg.sender:type_name -> wxproto.WxString @@ -510,27 +511,27 @@ var file_proto_wxproto_img_msg_proto_depIdxs = []int32{ 0, // [0:5] is the sub-list for field type_name } -func init() { file_proto_wxproto_img_msg_proto_init() } -func file_proto_wxproto_img_msg_proto_init() { - if File_proto_wxproto_img_msg_proto != nil { +func init() { file_img_msg_proto_init() } +func file_img_msg_proto_init() { + if File_img_msg_proto != nil { return } - file_proto_wxproto_wx_msg_proto_init() + file_wx_msg_proto_init() type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_img_msg_proto_rawDesc), len(file_proto_wxproto_img_msg_proto_rawDesc)), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_img_msg_proto_rawDesc), len(file_img_msg_proto_rawDesc)), NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_proto_wxproto_img_msg_proto_goTypes, - DependencyIndexes: file_proto_wxproto_img_msg_proto_depIdxs, - MessageInfos: file_proto_wxproto_img_msg_proto_msgTypes, + GoTypes: file_img_msg_proto_goTypes, + DependencyIndexes: file_img_msg_proto_depIdxs, + MessageInfos: file_img_msg_proto_msgTypes, }.Build() - File_proto_wxproto_img_msg_proto = out.File - file_proto_wxproto_img_msg_proto_goTypes = nil - file_proto_wxproto_img_msg_proto_depIdxs = nil + File_img_msg_proto = out.File + file_img_msg_proto_goTypes = nil + file_img_msg_proto_depIdxs = nil } diff --git a/onebot/proto/wxproto/img_msg.proto b/onebot/proto/img_msg.proto similarity index 88% rename from onebot/proto/wxproto/img_msg.proto rename to onebot/proto/img_msg.proto index ab8fa13..d280984 100644 --- a/onebot/proto/wxproto/img_msg.proto +++ b/onebot/proto/img_msg.proto @@ -2,9 +2,9 @@ syntax = "proto3"; package wxproto; -option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"; +option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto;wxproto"; -import "proto/wxproto/wx_msg.proto"; +import "wx_msg.proto"; // 发送图片消息的protobuf结构 message WxSendImgMsg { @@ -46,11 +46,11 @@ message WxSendImgMsg { message ImgMsgHeader { bytes flag = 1; - int64 msg_id = 2; + int64 session_id = 2; bytes client_proof = 3; - int64 unknown4 = 4; - bytes sys_info = 5; - int32 unknown6 = 6; + int64 device_id = 4; + bytes platform = 5; + int32 version = 6; } message ImgMsgExtra { diff --git a/onebot/proto/wxproto/text_msg.pb.go b/onebot/proto/text_msg.pb.go similarity index 72% rename from onebot/proto/wxproto/text_msg.pb.go rename to onebot/proto/text_msg.pb.go index 63c83b9..1f25abb 100644 --- a/onebot/proto/wxproto/text_msg.pb.go +++ b/onebot/proto/text_msg.pb.go @@ -2,7 +2,7 @@ // versions: // protoc-gen-go v1.36.11 // protoc v3.5.1 -// source: proto/wxproto/text_msg.proto +// source: text_msg.proto package wxproto @@ -32,7 +32,7 @@ type WxSendTextMsg struct { func (x *WxSendTextMsg) Reset() { *x = WxSendTextMsg{} - mi := &file_proto_wxproto_text_msg_proto_msgTypes[0] + mi := &file_text_msg_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -44,7 +44,7 @@ func (x *WxSendTextMsg) String() string { func (*WxSendTextMsg) ProtoMessage() {} func (x *WxSendTextMsg) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_text_msg_proto_msgTypes[0] + mi := &file_text_msg_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -57,7 +57,7 @@ func (x *WxSendTextMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use WxSendTextMsg.ProtoReflect.Descriptor instead. func (*WxSendTextMsg) Descriptor() ([]byte, []int) { - return file_proto_wxproto_text_msg_proto_rawDescGZIP(), []int{0} + return file_text_msg_proto_rawDescGZIP(), []int{0} } func (x *WxSendTextMsg) GetType() int32 { @@ -88,7 +88,7 @@ type WxSendTextBody struct { func (x *WxSendTextBody) Reset() { *x = WxSendTextBody{} - mi := &file_proto_wxproto_text_msg_proto_msgTypes[1] + mi := &file_text_msg_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -100,7 +100,7 @@ func (x *WxSendTextBody) String() string { func (*WxSendTextBody) ProtoMessage() {} func (x *WxSendTextBody) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_text_msg_proto_msgTypes[1] + mi := &file_text_msg_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -113,7 +113,7 @@ func (x *WxSendTextBody) ProtoReflect() protoreflect.Message { // Deprecated: Use WxSendTextBody.ProtoReflect.Descriptor instead. func (*WxSendTextBody) Descriptor() ([]byte, []int) { - return file_proto_wxproto_text_msg_proto_rawDescGZIP(), []int{1} + return file_text_msg_proto_rawDescGZIP(), []int{1} } func (x *WxSendTextBody) GetReceiver() *WxString { @@ -158,11 +158,11 @@ func (x *WxSendTextBody) GetXml() []byte { return nil } -var File_proto_wxproto_text_msg_proto protoreflect.FileDescriptor +var File_text_msg_proto protoreflect.FileDescriptor -const file_proto_wxproto_text_msg_proto_rawDesc = "" + +const file_text_msg_proto_rawDesc = "" + "\n" + - "\x1cproto/wxproto/text_msg.proto\x12\awxproto\x1a\x1aproto/wxproto/wx_msg.proto\"P\n" + + "\x0etext_msg.proto\x12\awxproto\x1a\fwx_msg.proto\"P\n" + "\rWxSendTextMsg\x12\x12\n" + "\x04type\x18\x01 \x01(\x05R\x04type\x12+\n" + "\x04body\x18\x02 \x01(\v2\x17.wxproto.WxSendTextBodyR\x04body\"\xb4\x01\n" + @@ -172,27 +172,27 @@ const file_proto_wxproto_text_msg_proto_rawDesc = "" + "\x04flag\x18\x03 \x01(\x05R\x04flag\x12\x1c\n" + "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12\x15\n" + "\x06msg_id\x18\x05 \x01(\x03R\x05msgId\x12\x10\n" + - "\x03xml\x18\x06 \x01(\fR\x03xmlB>ZZ wxproto.WxSendTextBody 2, // 1: wxproto.WxSendTextBody.receiver:type_name -> wxproto.WxString 2, // [2:2] is the sub-list for method output_type @@ -202,27 +202,27 @@ var file_proto_wxproto_text_msg_proto_depIdxs = []int32{ 0, // [0:2] is the sub-list for field type_name } -func init() { file_proto_wxproto_text_msg_proto_init() } -func file_proto_wxproto_text_msg_proto_init() { - if File_proto_wxproto_text_msg_proto != nil { +func init() { file_text_msg_proto_init() } +func file_text_msg_proto_init() { + if File_text_msg_proto != nil { return } - file_proto_wxproto_wx_msg_proto_init() + file_wx_msg_proto_init() type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_text_msg_proto_rawDesc), len(file_proto_wxproto_text_msg_proto_rawDesc)), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_text_msg_proto_rawDesc), len(file_text_msg_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_proto_wxproto_text_msg_proto_goTypes, - DependencyIndexes: file_proto_wxproto_text_msg_proto_depIdxs, - MessageInfos: file_proto_wxproto_text_msg_proto_msgTypes, + GoTypes: file_text_msg_proto_goTypes, + DependencyIndexes: file_text_msg_proto_depIdxs, + MessageInfos: file_text_msg_proto_msgTypes, }.Build() - File_proto_wxproto_text_msg_proto = out.File - file_proto_wxproto_text_msg_proto_goTypes = nil - file_proto_wxproto_text_msg_proto_depIdxs = nil + File_text_msg_proto = out.File + file_text_msg_proto_goTypes = nil + file_text_msg_proto_depIdxs = nil } diff --git a/onebot/proto/wxproto/text_msg.proto b/onebot/proto/text_msg.proto similarity index 86% rename from onebot/proto/wxproto/text_msg.proto rename to onebot/proto/text_msg.proto index 52644cd..8df4fbb 100644 --- a/onebot/proto/wxproto/text_msg.proto +++ b/onebot/proto/text_msg.proto @@ -2,9 +2,9 @@ syntax = "proto3"; package wxproto; -option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"; +option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto;wxproto"; -import "proto/wxproto/wx_msg.proto"; +import "wx_msg.proto"; // 发送文本消息的protobuf结构 message WxSendTextMsg { diff --git a/onebot/proto/wxproto/video_msg.pb.go b/onebot/proto/video_msg.pb.go similarity index 80% rename from onebot/proto/wxproto/video_msg.pb.go rename to onebot/proto/video_msg.pb.go index 700d594..4508e5e 100644 --- a/onebot/proto/wxproto/video_msg.pb.go +++ b/onebot/proto/video_msg.pb.go @@ -2,7 +2,7 @@ // versions: // protoc-gen-go v1.36.11 // protoc v3.5.1 -// source: proto/wxproto/video_msg.proto +// source: video_msg.proto package wxproto @@ -63,7 +63,7 @@ type WxSendVideoMsg struct { func (x *WxSendVideoMsg) Reset() { *x = WxSendVideoMsg{} - mi := &file_proto_wxproto_video_msg_proto_msgTypes[0] + mi := &file_video_msg_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -75,7 +75,7 @@ func (x *WxSendVideoMsg) String() string { func (*WxSendVideoMsg) ProtoMessage() {} func (x *WxSendVideoMsg) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_video_msg_proto_msgTypes[0] + mi := &file_video_msg_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -88,7 +88,7 @@ func (x *WxSendVideoMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use WxSendVideoMsg.ProtoReflect.Descriptor instead. func (*WxSendVideoMsg) Descriptor() ([]byte, []int) { - return file_proto_wxproto_video_msg_proto_rawDescGZIP(), []int{0} + return file_video_msg_proto_rawDescGZIP(), []int{0} } func (x *WxSendVideoMsg) GetHeader() *VideoMsgHeader { @@ -304,18 +304,18 @@ func (x *WxSendVideoMsg) GetVideoSize3() int32 { type VideoMsgHeader struct { state protoimpl.MessageState `protogen:"open.v1"` Flag []byte `protobuf:"bytes,1,opt,name=flag,proto3" json:"flag,omitempty"` - MsgId int64 `protobuf:"varint,2,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"` + SessionId int64 `protobuf:"varint,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` ClientProof []byte `protobuf:"bytes,3,opt,name=client_proof,json=clientProof,proto3" json:"client_proof,omitempty"` - Unknown4 int64 `protobuf:"varint,4,opt,name=unknown4,proto3" json:"unknown4,omitempty"` - SysInfo []byte `protobuf:"bytes,5,opt,name=sys_info,json=sysInfo,proto3" json:"sys_info,omitempty"` - Unknown6 int32 `protobuf:"varint,6,opt,name=unknown6,proto3" json:"unknown6,omitempty"` + DeviceId int64 `protobuf:"varint,4,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + Platform []byte `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` + Version int32 `protobuf:"varint,6,opt,name=version,proto3" json:"version,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *VideoMsgHeader) Reset() { *x = VideoMsgHeader{} - mi := &file_proto_wxproto_video_msg_proto_msgTypes[1] + mi := &file_video_msg_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -327,7 +327,7 @@ func (x *VideoMsgHeader) String() string { func (*VideoMsgHeader) ProtoMessage() {} func (x *VideoMsgHeader) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_video_msg_proto_msgTypes[1] + mi := &file_video_msg_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -340,7 +340,7 @@ func (x *VideoMsgHeader) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoMsgHeader.ProtoReflect.Descriptor instead. func (*VideoMsgHeader) Descriptor() ([]byte, []int) { - return file_proto_wxproto_video_msg_proto_rawDescGZIP(), []int{1} + return file_video_msg_proto_rawDescGZIP(), []int{1} } func (x *VideoMsgHeader) GetFlag() []byte { @@ -350,9 +350,9 @@ func (x *VideoMsgHeader) GetFlag() []byte { return nil } -func (x *VideoMsgHeader) GetMsgId() int64 { +func (x *VideoMsgHeader) GetSessionId() int64 { if x != nil { - return x.MsgId + return x.SessionId } return 0 } @@ -364,23 +364,23 @@ func (x *VideoMsgHeader) GetClientProof() []byte { return nil } -func (x *VideoMsgHeader) GetUnknown4() int64 { +func (x *VideoMsgHeader) GetDeviceId() int64 { if x != nil { - return x.Unknown4 + return x.DeviceId } return 0 } -func (x *VideoMsgHeader) GetSysInfo() []byte { +func (x *VideoMsgHeader) GetPlatform() []byte { if x != nil { - return x.SysInfo + return x.Platform } return nil } -func (x *VideoMsgHeader) GetUnknown6() int32 { +func (x *VideoMsgHeader) GetVersion() int32 { if x != nil { - return x.Unknown6 + return x.Version } return 0 } @@ -395,7 +395,7 @@ type VideoMsgExtra struct { func (x *VideoMsgExtra) Reset() { *x = VideoMsgExtra{} - mi := &file_proto_wxproto_video_msg_proto_msgTypes[2] + mi := &file_video_msg_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -407,7 +407,7 @@ func (x *VideoMsgExtra) String() string { func (*VideoMsgExtra) ProtoMessage() {} func (x *VideoMsgExtra) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_video_msg_proto_msgTypes[2] + mi := &file_video_msg_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -420,7 +420,7 @@ func (x *VideoMsgExtra) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoMsgExtra.ProtoReflect.Descriptor instead. func (*VideoMsgExtra) Descriptor() ([]byte, []int) { - return file_proto_wxproto_video_msg_proto_rawDescGZIP(), []int{2} + return file_video_msg_proto_rawDescGZIP(), []int{2} } func (x *VideoMsgExtra) GetField1() int32 { @@ -437,11 +437,11 @@ func (x *VideoMsgExtra) GetField2() []byte { return nil } -var File_proto_wxproto_video_msg_proto protoreflect.FileDescriptor +var File_video_msg_proto protoreflect.FileDescriptor -const file_proto_wxproto_video_msg_proto_rawDesc = "" + +const file_video_msg_proto_rawDesc = "" + "\n" + - "\x1dproto/wxproto/video_msg.proto\x12\awxproto\"\xa7\a\n" + + "\x0fvideo_msg.proto\x12\awxproto\"\xa7\a\n" + "\x0eWxSendVideoMsg\x12/\n" + "\x06header\x18\x01 \x01(\v2\x17.wxproto.VideoMsgHeaderR\x06header\x12\"\n" + "\rclient_msg_id\x18\x02 \x01(\fR\vclientMsgId\x12\x16\n" + @@ -476,37 +476,38 @@ const file_proto_wxproto_video_msg_proto_rawDesc = "" + "\bcdn_key3\x181 \x01(\fR\acdnKey3\x12\x19\n" + "\baes_key3\x182 \x01(\fR\aaesKey3\x12\x1f\n" + "\vvideo_size3\x183 \x01(\x05R\n" + - "videoSize3\"\xb1\x01\n" + + "videoSize3\"\xb9\x01\n" + "\x0eVideoMsgHeader\x12\x12\n" + - "\x04flag\x18\x01 \x01(\fR\x04flag\x12\x15\n" + - "\x06msg_id\x18\x02 \x01(\x03R\x05msgId\x12!\n" + - "\fclient_proof\x18\x03 \x01(\fR\vclientProof\x12\x1a\n" + - "\bunknown4\x18\x04 \x01(\x03R\bunknown4\x12\x19\n" + - "\bsys_info\x18\x05 \x01(\fR\asysInfo\x12\x1a\n" + - "\bunknown6\x18\x06 \x01(\x05R\bunknown6\"?\n" + + "\x04flag\x18\x01 \x01(\fR\x04flag\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\x03R\tsessionId\x12!\n" + + "\fclient_proof\x18\x03 \x01(\fR\vclientProof\x12\x1b\n" + + "\tdevice_id\x18\x04 \x01(\x03R\bdeviceId\x12\x1a\n" + + "\bplatform\x18\x05 \x01(\fR\bplatform\x12\x18\n" + + "\aversion\x18\x06 \x01(\x05R\aversion\"?\n" + "\rVideoMsgExtra\x12\x16\n" + "\x06field1\x18\x01 \x01(\x05R\x06field1\x12\x16\n" + - "\x06field2\x18\x02 \x01(\fR\x06field2B>ZZ wxproto.VideoMsgHeader 2, // 1: wxproto.WxSendVideoMsg.unknown7:type_name -> wxproto.VideoMsgExtra 2, // 2: wxproto.WxSendVideoMsg.unknown10:type_name -> wxproto.VideoMsgExtra @@ -517,26 +518,26 @@ var file_proto_wxproto_video_msg_proto_depIdxs = []int32{ 0, // [0:3] is the sub-list for field type_name } -func init() { file_proto_wxproto_video_msg_proto_init() } -func file_proto_wxproto_video_msg_proto_init() { - if File_proto_wxproto_video_msg_proto != nil { +func init() { file_video_msg_proto_init() } +func file_video_msg_proto_init() { + if File_video_msg_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_video_msg_proto_rawDesc), len(file_proto_wxproto_video_msg_proto_rawDesc)), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_video_msg_proto_rawDesc), len(file_video_msg_proto_rawDesc)), NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_proto_wxproto_video_msg_proto_goTypes, - DependencyIndexes: file_proto_wxproto_video_msg_proto_depIdxs, - MessageInfos: file_proto_wxproto_video_msg_proto_msgTypes, + GoTypes: file_video_msg_proto_goTypes, + DependencyIndexes: file_video_msg_proto_depIdxs, + MessageInfos: file_video_msg_proto_msgTypes, }.Build() - File_proto_wxproto_video_msg_proto = out.File - file_proto_wxproto_video_msg_proto_goTypes = nil - file_proto_wxproto_video_msg_proto_depIdxs = nil + File_video_msg_proto = out.File + file_video_msg_proto_goTypes = nil + file_video_msg_proto_depIdxs = nil } diff --git a/onebot/proto/wxproto/video_msg.proto b/onebot/proto/video_msg.proto similarity index 90% rename from onebot/proto/wxproto/video_msg.proto rename to onebot/proto/video_msg.proto index d1ff72d..210d18f 100644 --- a/onebot/proto/wxproto/video_msg.proto +++ b/onebot/proto/video_msg.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package wxproto; -option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"; +option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto;wxproto"; // 发送视频消息的protobuf结构 message WxSendVideoMsg { @@ -43,11 +43,11 @@ message WxSendVideoMsg { message VideoMsgHeader { bytes flag = 1; - int64 msg_id = 2; + int64 session_id = 2; bytes client_proof = 3; - int64 unknown4 = 4; - bytes sys_info = 5; - int32 unknown6 = 6; + int64 device_id = 4; + bytes platform = 5; + int32 version = 6; } message VideoMsgExtra { diff --git a/onebot/proto/wxproto/wx_msg.pb.go b/onebot/proto/wx_msg.pb.go similarity index 84% rename from onebot/proto/wxproto/wx_msg.pb.go rename to onebot/proto/wx_msg.pb.go index 61f0e30..36e7ba8 100644 --- a/onebot/proto/wxproto/wx_msg.pb.go +++ b/onebot/proto/wx_msg.pb.go @@ -2,7 +2,7 @@ // versions: // protoc-gen-go v1.36.11 // protoc v3.5.1 -// source: proto/wxproto/wx_msg.proto +// source: wx_msg.proto package wxproto @@ -32,7 +32,7 @@ type WxRecvMsg struct { func (x *WxRecvMsg) Reset() { *x = WxRecvMsg{} - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[0] + mi := &file_wx_msg_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -44,7 +44,7 @@ func (x *WxRecvMsg) String() string { func (*WxRecvMsg) ProtoMessage() {} func (x *WxRecvMsg) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[0] + mi := &file_wx_msg_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -57,7 +57,7 @@ func (x *WxRecvMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use WxRecvMsg.ProtoReflect.Descriptor instead. func (*WxRecvMsg) Descriptor() ([]byte, []int) { - return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{0} + return file_wx_msg_proto_rawDescGZIP(), []int{0} } func (x *WxRecvMsg) GetRecvType() int32 { @@ -85,7 +85,7 @@ type WxRecvMsgWrapper struct { func (x *WxRecvMsgWrapper) Reset() { *x = WxRecvMsgWrapper{} - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[1] + mi := &file_wx_msg_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -97,7 +97,7 @@ func (x *WxRecvMsgWrapper) String() string { func (*WxRecvMsgWrapper) ProtoMessage() {} func (x *WxRecvMsgWrapper) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[1] + mi := &file_wx_msg_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -110,7 +110,7 @@ func (x *WxRecvMsgWrapper) ProtoReflect() protoreflect.Message { // Deprecated: Use WxRecvMsgWrapper.ProtoReflect.Descriptor instead. func (*WxRecvMsgWrapper) Descriptor() ([]byte, []int) { - return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{1} + return file_wx_msg_proto_rawDescGZIP(), []int{1} } func (x *WxRecvMsgWrapper) GetMsgType() int32 { @@ -138,7 +138,7 @@ type WxRecvMsgBody struct { func (x *WxRecvMsgBody) Reset() { *x = WxRecvMsgBody{} - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[2] + mi := &file_wx_msg_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -150,7 +150,7 @@ func (x *WxRecvMsgBody) String() string { func (*WxRecvMsgBody) ProtoMessage() {} func (x *WxRecvMsgBody) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[2] + mi := &file_wx_msg_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -163,7 +163,7 @@ func (x *WxRecvMsgBody) ProtoReflect() protoreflect.Message { // Deprecated: Use WxRecvMsgBody.ProtoReflect.Descriptor instead. func (*WxRecvMsgBody) Descriptor() ([]byte, []int) { - return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{2} + return file_wx_msg_proto_rawDescGZIP(), []int{2} } func (x *WxRecvMsgBody) GetMsgType() int32 { @@ -191,7 +191,7 @@ type WxRecvMsgContent struct { func (x *WxRecvMsgContent) Reset() { *x = WxRecvMsgContent{} - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[3] + mi := &file_wx_msg_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -203,7 +203,7 @@ func (x *WxRecvMsgContent) String() string { func (*WxRecvMsgContent) ProtoMessage() {} func (x *WxRecvMsgContent) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[3] + mi := &file_wx_msg_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -216,7 +216,7 @@ func (x *WxRecvMsgContent) ProtoReflect() protoreflect.Message { // Deprecated: Use WxRecvMsgContent.ProtoReflect.Descriptor instead. func (*WxRecvMsgContent) Descriptor() ([]byte, []int) { - return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{3} + return file_wx_msg_proto_rawDescGZIP(), []int{3} } func (x *WxRecvMsgContent) GetDataLen() int32 { @@ -257,7 +257,7 @@ type WxRecvMsgData struct { func (x *WxRecvMsgData) Reset() { *x = WxRecvMsgData{} - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[4] + mi := &file_wx_msg_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -269,7 +269,7 @@ func (x *WxRecvMsgData) String() string { func (*WxRecvMsgData) ProtoMessage() {} func (x *WxRecvMsgData) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[4] + mi := &file_wx_msg_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -282,7 +282,7 @@ func (x *WxRecvMsgData) ProtoReflect() protoreflect.Message { // Deprecated: Use WxRecvMsgData.ProtoReflect.Descriptor instead. func (*WxRecvMsgData) Descriptor() ([]byte, []int) { - return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{4} + return file_wx_msg_proto_rawDescGZIP(), []int{4} } func (x *WxRecvMsgData) GetId() int64 { @@ -400,7 +400,7 @@ type WxString struct { func (x *WxString) Reset() { *x = WxString{} - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[5] + mi := &file_wx_msg_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -412,7 +412,7 @@ func (x *WxString) String() string { func (*WxString) ProtoMessage() {} func (x *WxString) ProtoReflect() protoreflect.Message { - mi := &file_proto_wxproto_wx_msg_proto_msgTypes[5] + mi := &file_wx_msg_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -425,7 +425,7 @@ func (x *WxString) ProtoReflect() protoreflect.Message { // Deprecated: Use WxString.ProtoReflect.Descriptor instead. func (*WxString) Descriptor() ([]byte, []int) { - return file_proto_wxproto_wx_msg_proto_rawDescGZIP(), []int{5} + return file_wx_msg_proto_rawDescGZIP(), []int{5} } func (x *WxString) GetValue() string { @@ -435,11 +435,11 @@ func (x *WxString) GetValue() string { return "" } -var File_proto_wxproto_wx_msg_proto protoreflect.FileDescriptor +var File_wx_msg_proto protoreflect.FileDescriptor -const file_proto_wxproto_wx_msg_proto_rawDesc = "" + +const file_wx_msg_proto_rawDesc = "" + "\n" + - "\x1aproto/wxproto/wx_msg.proto\x12\awxproto\"]\n" + + "\fwx_msg.proto\x12\awxproto\"]\n" + "\tWxRecvMsg\x12\x1b\n" + "\trecv_type\x18\x01 \x01(\x05R\brecvType\x123\n" + "\awrapper\x18\x02 \x01(\v2\x19.wxproto.WxRecvMsgWrapperR\awrapper\"Y\n" + @@ -471,22 +471,22 @@ const file_proto_wxproto_wx_msg_proto_rawDesc = "" + "\textra_xml\x18\x0e \x01(\fR\bextraXml\x12\x1c\n" + "\tunknown15\x18\x0f \x01(\x05R\tunknown15\" \n" + "\bWxString\x12\x14\n" + - "\x05value\x18\x01 \x01(\tR\x05valueB>ZZ wxproto.WxRecvMsgWrapper 2, // 1: wxproto.WxRecvMsgWrapper.body:type_name -> wxproto.WxRecvMsgBody 3, // 2: wxproto.WxRecvMsgBody.content:type_name -> wxproto.WxRecvMsgContent @@ -509,26 +509,26 @@ var file_proto_wxproto_wx_msg_proto_depIdxs = []int32{ 0, // [0:7] is the sub-list for field type_name } -func init() { file_proto_wxproto_wx_msg_proto_init() } -func file_proto_wxproto_wx_msg_proto_init() { - if File_proto_wxproto_wx_msg_proto != nil { +func init() { file_wx_msg_proto_init() } +func file_wx_msg_proto_init() { + if File_wx_msg_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_wx_msg_proto_rawDesc), len(file_proto_wxproto_wx_msg_proto_rawDesc)), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_wx_msg_proto_rawDesc), len(file_wx_msg_proto_rawDesc)), NumEnums: 0, NumMessages: 6, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_proto_wxproto_wx_msg_proto_goTypes, - DependencyIndexes: file_proto_wxproto_wx_msg_proto_depIdxs, - MessageInfos: file_proto_wxproto_wx_msg_proto_msgTypes, + GoTypes: file_wx_msg_proto_goTypes, + DependencyIndexes: file_wx_msg_proto_depIdxs, + MessageInfos: file_wx_msg_proto_msgTypes, }.Build() - File_proto_wxproto_wx_msg_proto = out.File - file_proto_wxproto_wx_msg_proto_goTypes = nil - file_proto_wxproto_wx_msg_proto_depIdxs = nil + File_wx_msg_proto = out.File + file_wx_msg_proto_goTypes = nil + file_wx_msg_proto_depIdxs = nil } diff --git a/onebot/proto/wxproto/wx_msg.proto b/onebot/proto/wx_msg.proto similarity index 97% rename from onebot/proto/wxproto/wx_msg.proto rename to onebot/proto/wx_msg.proto index d79fb61..52b8433 100644 --- a/onebot/proto/wxproto/wx_msg.proto +++ b/onebot/proto/wx_msg.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package wxproto; -option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"; +option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto;wxproto"; // 最外层消息 message WxRecvMsg { diff --git a/onebot/protobuf_handler.go b/onebot/protobuf_handler.go index 506f614..c5c7459 100644 --- a/onebot/protobuf_handler.go +++ b/onebot/protobuf_handler.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto" + wxproto "github.com/yincongcyincong/weixin-macos/onebot/proto" "google.golang.org/protobuf/proto" ) @@ -82,11 +82,17 @@ func HandleProtobufMsg(payload map[string]interface{}) ([]byte, error) { } selfId := receiver + if strings.Contains(receiver, "@chatroom") { + selfId = sender + } msgType := "private" groupId := "" senderUser := sender senderNickname := "" messages := getMessagesFromProto(content, sender, data.MediaContent) + if len(messages) == 0 { + return nil, fmt.Errorf("protobuf_msg: no messages found") + } if strings.Contains(sender, "@chatroom") { msgType = "group" @@ -133,7 +139,6 @@ func HandleProtobufMsg(payload map[string]interface{}) ([]byte, error) { } } - myWechatId = selfId if groupId != "" { userID2NicknameMap.Store(groupId+"_"+senderUser, senderNickname) } @@ -230,7 +235,7 @@ func classifyMessage(content string, mediaContent []byte) *Message { return &Message{Type: "face", Data: &SendRequestData{Text: content}} case strings.HasPrefix(content, "` + fmt.Sprintf("%d", info.MsgType) + `` xml += `` + fmt.Sprintf("%d", createTime) + `` xml += `` + escapeXmlStr(info.Msgsource) + `` - xml += `` + escapeXmlStr(info.DisplayName) + `` + xml += `` xml += `` + escapeXmlStr(info.MsgId) + `` xml += `` + escapeXmlStr(info.MsgSender) + `` xml += `` + escapeXmlStr(info.MsgContent) + `` xml += `` xml += `` - xml += `` + escapeXmlStr(info.MsgSender) + `` + xml += `` + escapeXmlStr(selfWxid) + `` return xml } @@ -162,16 +160,12 @@ func escapeXmlStr(s string) string { return result } -// generateRandomBytes 生成随机字节 -func generateRandomBytes(n int) []byte { +// generateClientProof 生成n字节可见字符(模拟微信的client_proof格式) +func generateClientProof(n int) []byte { + const chars = "0123456789abcdefghijklmnopqrstuvwxyz" b := make([]byte, n) for i := range b { - b[i] = byte(rand.Intn(256)) + b[i] = chars[rand.Intn(len(chars))] } return b } - -// generateDeviceId 动态生成设备ID (大整数,高位置1模拟真实设备) -func generateDeviceId() uint64 { - return rand.Uint64() | (0xFFFFFFFF << 32) -} diff --git a/onebot/resp_parser.go b/onebot/resp_parser.go new file mode 100644 index 0000000..23372ab --- /dev/null +++ b/onebot/resp_parser.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + + "google.golang.org/protobuf/proto" + + wxproto "github.com/yincongcyincong/weixin-macos/onebot/proto" +) + +// ParseSendMsgResponse 解析发送消息响应,提取 BaseResponse 中的错误码 +// 不同消息类型的响应格式不同: +// - text/video/image/file/reply: BaseResponse 在 field 1 → SendMsgResponse +// - voice: BaseResponse 在 field 10 → SendVoiceMsgResponse +// +// 返回 (ret, errMsg, error) +// 如果两种格式都无法提取到有效的 BaseResponse,返回 ret=0 视为成功 +func ParseSendMsgResponse(data []byte) (int32, string, error) { + // 尝试格式1: BaseResponse 在 field 1 (text/video/image/file/reply) + resp1 := &wxproto.SendMsgResponse{} + if err := proto.Unmarshal(data, resp1); err == nil { + if resp1.BaseResponse != nil && resp1.BaseResponse.Ret != nil { + errMsg := "" + if resp1.BaseResponse.ErrMsg != nil { + errMsg = resp1.BaseResponse.ErrMsg.GetMsg() + } + return *resp1.BaseResponse.Ret, errMsg, nil + } + } + + // 尝试格式2: BaseResponse 在 field 10 (voice) + resp2 := &wxproto.SendVoiceMsgResponse{} + if err := proto.Unmarshal(data, resp2); err == nil { + if resp2.BaseResponse != nil && resp2.BaseResponse.Ret != nil { + errMsg := "" + if resp2.BaseResponse.ErrMsg != nil { + errMsg = resp2.BaseResponse.ErrMsg.GetMsg() + } + return *resp2.BaseResponse.Ret, errMsg, nil + } + } + + // 两种格式都未提取到 BaseResponse,视为成功 + return 0, "", fmt.Errorf("unable to extract BaseResponse from response") +} diff --git a/onebot/script.js b/onebot/script.js index e2e0d4d..327bc8b 100644 --- a/onebot/script.js +++ b/onebot/script.js @@ -1,12 +1,109 @@ -var targetPath = "/Applications/WeChat.app/Contents/Resources/wechat.dylib"; +var targetPath = "/Applications/WeChat.app/Contents/MacOS/WeChat"; var module = Process.enumerateModules().find(function(m) { return m.path === targetPath; }); -const baseAddr = module.base; -if (!baseAddr) { - console.error("[!] 找不到 WeChat 模块基址,请检查进程名。"); +if (!module) { + throw new Error("[-] Cannot find module: " + targetPath); +} +var moduleBase = module.base; +console.log("[+] WeChat module base: " + moduleBase); + +// Enumerate readable ranges within 500MB from module base to search for "req2buf" +var searchSize = 1000 * 1024 * 1024; +var searchEnd = moduleBase.add(searchSize); +var _req2bufSearchAddr = null; +var baseAddr = null; + +var ranges = Process.enumerateRanges("r--").filter(function(r) { + var rangeEnd = r.base.add(r.size); + return r.base.compare(searchEnd) < 0 && rangeEnd.compare(moduleBase) > 0; +}); + +console.log("[+] Found " + ranges.length + " readable ranges within 1000MB window"); + +var pending = ranges.length; +if (pending === 0) { + throw new Error("[-] No readable ranges found within 1000MB from module base"); +} + +ranges.forEach(function(r) { + Memory.scan(r.base, r.size, "72 65 71 32 62 75 66", { + onMatch: function(address, size) { + if (_req2bufSearchAddr === null) { + var rangeInfo = Process.findRangeByAddress(address); + if (rangeInfo) { + if (rangeInfo.size > 100 * 1024 * 1024) { + _req2bufSearchAddr = address; + console.log("[+] Range size > 100MB, accepted as base address"); + } + } + } + }, + onError: function(reason) { + // skip unreadable sub-pages + }, + onComplete: function() { + pending--; + if (pending === 0) { + if (_req2bufSearchAddr === null) { + throw new Error("[-] Cannot find 'req2buf' keyword in a range > 100MB"); + } + + var foundRange = Process.findRangeByAddress(_req2bufSearchAddr); + baseAddr = foundRange.base; + console.log("[+] Base address from range: " + baseAddr); + console.log("[+] Range size: " + foundRange.size); + + initAddresses(); + } + } + }); +}); + +function initAddresses() { + // 文本消息全局变量 (new_text.js approach) + blrX8Addr = baseAddr.add({{.blrX8Addr}}); + autoBufferWriteFunc = baseAddr.add({{.autoBufferWriteFunc}}); + + // 双方公共使用的地址 + req2bufEnterAddr = baseAddr.add({{.req2bufEnterAddr}}); + req2bufExitAddr = baseAddr.add({{.req2bufExitAddr}}); + sendFuncAddr = baseAddr.add({{.sendFuncAddr}}); + buf2RespAddr = baseAddr.add({{.buf2RespAddr}}); + + uploadImageAddr = baseAddr.add({{.uploadImageAddr}}); + cndOnCompleteAddr = baseAddr.add({{.cndOnCompleteAddr}}); + + uploadGetCallbackWrapperAddr = baseAddr.add({{.uploadGetCallbackWrapperAddr}}); + uploadGetCallbackWrapperFuncAddr = baseAddr.add({{.uploadGetCallbackWrapperFuncAddr}}); + uploadOnCompleteAddr = baseAddr.add({{.uploadOnCompleteAddr}}); + uploadOnCompleteFuncAddr = baseAddr.add({{.uploadOnCompleteFuncAddr}}); + downloadImagAddr = baseAddr.add({{.downloadImagAddr}}); + startDownloadMedia = baseAddr.add({{.startDownloadMedia}}); + downloadFileAddr = baseAddr.add({{.downloadFileAddr}}); + downloadVideoAddr = baseAddr.add({{.downloadVideoAddr}}); + + sendMessageCallbackFunc = baseAddr.add(0x0); + imgMessageCallbackFunc = baseAddr.add(0x0); + videoMessageCallbackFunc = baseAddr.add(0x0); + replyMessageCallbackFunc = baseAddr.add(0x0); + voiceMessageCallbackFunc = baseAddr.add(0x0); + + setupRetOneStub(); // 必须同步先执行,初始化fakeVtable + setImmediate(setupSendTextMessageDynamic); + setImmediate(setupSendFileMessageDynamic); + setImmediate(setupSendFileUploadMessageDynamic); + setImmediate(attachBlrX8Hook); + setImmediate(AttachSendFunc); + setImmediate(attachReq2buf); + setImmediate(setupSendImgMessageDynamic); + setImmediate(attachUploadMedia); + setImmediate(patchCdnOnComplete); + setImmediate(attachGetCallbackFromWrapper); + setImmediate(setupSendReplyMessageDynamic); + setImmediate(setupDownloadFileDynamic); + setImmediate(setReceiver); } -console.log("[+] WeChat base address: " + baseAddr); // -------------------------基础函数分区------------------------- function hexToByteArray(hexStr) { @@ -36,72 +133,79 @@ function generateAESKey() { return key; } +function fillUploadX1AndStart(idAddr, pathAddr, x1Buffer, receiver, md5, filePath, payloadHex) { + if (uploadGlobalX0.equals(ptr(0))) { + console.error("[!] uploadGlobalX0 尚未初始化,请等待 hook 捕获"); + return "fail"; + } + + const payload = hexToByteArray(payloadHex); + patchString(idAddr, receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1"); + patchString(md5Addr, md5); + patchString(uploadAesKeyAddr, generateAESKey()); + patchString(pathAddr, filePath); + + x1Buffer.writeByteArray(payload); + x1Buffer.writePointer(uploadFunc1Addr); + x1Buffer.add(0x08).writePointer(uploadFunc2Addr); + x1Buffer.add(0x48).writePointer(idAddr); + x1Buffer.add(0x68).writeUtf8String(receiver); + x1Buffer.add(0xa8).writePointer(md5Addr); + x1Buffer.add(0xe8).writePointer(pathAddr); + x1Buffer.add(0x118).writePointer(pathAddr); + x1Buffer.add(0x148).writePointer(pathAddr); + x1Buffer.add(0x200).writePointer(uploadAesKeyAddr); + + const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']); + return startUploadMedia(uploadGlobalX0, x1Buffer); +} + // -------------------------基础函数分区------------------------- // -------------------------全局变量分区------------------------- -// 文本消息全局变量 -var textCallbackFuncAddr = baseAddr.add({{.textCallbackFuncAddr}}); -var protobufAddr = textCallbackFuncAddr.add(0x40); -var patchTextProtobufAddr = textCallbackFuncAddr.add(0x20); -var patchTextProtobufByte -var patchTextProtobufDeleteAddr = textCallbackFuncAddr.add(0x5C); -var patchTextProtobufDeleteByte +// 文本消息全局变量 (new_text.js approach) +var blrX8Addr; +var autoBufferWriteFunc; var textCgiAddr = ptr(0); var sendTextMessageAddr = ptr(0); var textMessageAddr = ptr(0); -var textProtoX1PayloadAddr = ptr(0); -var sendMessageCallbackFunc = baseAddr.add({{.sendMessageCallbackFunc}}); +var sendMessageCallbackFunc; +var retOneStub = ptr(0); +var fakeVtable = ptr(0); +var pendingInsertMsgAddr = ptr(0); // 等待buf2resp后清理的insertMsgAddr +var pendingSendMsgType = ""; // 等待buf2resp回调时使用的消息类型 +var pendingBuf2RespTaskId = 0; // 等待buf2resp匹配的taskId +var textProtoDataAddr = ptr(0); // 双方公共使用的地址 var triggerX1Payload; var triggerX0; -var req2bufEnterAddr = baseAddr.add({{.req2bufEnterAddr}}); -var req2bufExitAddr = baseAddr.add({{.req2bufExitAddr}}); -var sendFuncAddr = baseAddr.add({{.sendFuncAddr}}); +var req2bufEnterAddr; +var req2bufExitAddr; +var sendFuncAddr; var insertMsgAddr = ptr(0); var sendMsgType = ""; -var buf2RespAddr = baseAddr.add({{.buf2RespAddr}}); +var buf2RespAddr; -// 图片消息全局变量 -var imageCallbackFuncAddr = baseAddr.add({{.imageCallbackFuncAddr}}); -var imgProtobufAddr = imageCallbackFuncAddr.add(0x50); -var patchImgProtobufFunc1 = imageCallbackFuncAddr.add(0x10); -var patchImgProtobufFunc1Byte; -var patchImgProtobufFunc2 = imageCallbackFuncAddr.add(0x30); -var patchImgProtobufFunc2Byte; -var imgProtobufDeleteAddr = imageCallbackFuncAddr.add(0x6c); -var imgProtobufDeleteAddrByte; +var uploadImageAddr; +var cndOnCompleteAddr; +var imgMessageCallbackFunc; +var videoMessageCallbackFunc; -// 视频消息全局变量 -var videoCallbackFuncAddr = baseAddr.add({{.videoCallbackFuncAddr}}); -var videoProtobufAddr = videoCallbackFuncAddr.add(0x50); -var patchVideoProtobufFunc1 = videoCallbackFuncAddr.add(0x10); -var patchVideoProtobufFunc1Byte; -var patchVideoProtobufFunc2 = videoCallbackFuncAddr.add(0x30); -var patchVideoProtobufFunc2Byte; -var videoProtobufDeleteAddr = videoCallbackFuncAddr.add(0x6c); -var videoProtobufDeleteAddrByte; - -var uploadImageAddr = baseAddr.add({{.uploadImageAddr}}); -var cndOnCompleteAddr = baseAddr.add({{.cndOnCompleteAddr}}); -var imgMessageCallbackFunc = baseAddr.add({{.imgMessageCallbackFunc}}); -var videoMessageCallbackFunc = baseAddr.add({{.videoMessageCallbackFunc}}); - -var uploadGetCallbackWrapperAddr = baseAddr.add({{.uploadGetCallbackWrapperAddr}}); -var uploadGetCallbackWrapperFuncAddr = baseAddr.add({{.uploadGetCallbackWrapperFuncAddr}}); -var uploadOnCompleteAddr = baseAddr.add({{.uploadOnCompleteAddr}}); -var uploadOnCompleteFuncAddr = baseAddr.add({{.uploadOnCompleteFuncAddr}}); -var downloadImagAddr = baseAddr.add({{.downloadImagAddr}}); -var startDownloadMedia = baseAddr.add({{.startDownloadMedia}}) -var downloadFileAddr = baseAddr.add({{.downloadFileAddr}}) -var downloadVideoAddr = baseAddr.add({{.downloadVideoAddr}}) +var uploadGetCallbackWrapperAddr; +var uploadGetCallbackWrapperFuncAddr; +var uploadOnCompleteAddr; +var uploadOnCompleteFuncAddr; +var downloadImagAddr; +var startDownloadMedia; +var downloadFileAddr; +var downloadVideoAddr; var downloadGlobalX0; var downloadFileX1 = ptr(0) var fileIdAddr = ptr(0) -var fileMd5Addr = ptr(0) var downloadAesKeyAddr = ptr(0) var filePathAddr = ptr(0) var fileCdnUrlAddr = ptr(0) @@ -109,7 +213,6 @@ var uploadImageX1 = ptr(0); var imgCgiAddr = ptr(0); var sendImgMessageAddr = ptr(0); var imgMessageAddr = ptr(0); -var imgProtoX1PayloadAddr = ptr(0); var uploadGlobalX0 = ptr(0) var uploadFunc1Addr = ptr(0) var uploadFunc2Addr = ptr(0) @@ -122,11 +225,23 @@ var uploadCallback = ptr(0) var videoCgiAddr = ptr(0); var sendVideoMessageAddr = ptr(0); var videoMessageAddr = ptr(0); -var videoProtoX1PayloadAddr = ptr(0); var uploadVideoX1 = ptr(0); var videoIdAddr = ptr(0); var videoPathAddr1 = ptr(0) +// 语音消息全局变量 +var voiceMessageCallbackFunc; +var voiceCgiAddr = ptr(0); +var sendVoiceMessageAddr = ptr(0); +var voiceMessageAddr = ptr(0); +var uploadVoiceX1 = ptr(0); +var voiceIdAddr = ptr(0); +var voicePathAddr1 = ptr(0); +var voiceProtoHexGlobal = ""; +var voiceDurationGlobal = 0; +var voiceSilkDataLenGlobal = 0; +var voiceAudioDataAddr = ptr(0); + // 发送消息的全局变量 var taskIdGlobal = 0x20000090 // 最好比较大,不和原始的微信消息重复 @@ -139,21 +254,27 @@ var imgProtoHexGlobal = ""; var videoProtoHexGlobal = ""; // 回复消息protobuf全局变量 (从Go直接传入hex编码) var replyProtoHexGlobal = ""; +// 文件消息protobuf全局变量 (从Go直接传入hex编码) +var fileProtoHexGlobal = ""; +var fileUploadProtoHexGlobal = ""; + +// 文件消息全局变量 +var fileCgiAddr = ptr(0); +var sendFileMessageAddr = ptr(0); +var fileMessageAddr = ptr(0); +var uploadFileIdAddr = ptr(0); +var uploadFileX1 = ptr(0); + +// sendfileuploadmsg 全局变量 +var fileUploadCgiAddr = ptr(0); +var sendFileUploadMessageAddr = ptr(0); +var fileUploadMessageAddr = ptr(0); // 回复消息全局变量 -var replyCallbackFuncAddr = baseAddr.add({{.replyCallbackFuncAddr}}); -var replyProtobufAddr = replyCallbackFuncAddr.add(0x50); -var patchReplyProtobufFunc1 = replyCallbackFuncAddr.add(0x10); -var patchReplyProtobufFunc1Byte; -var patchReplyProtobufFunc2 = replyCallbackFuncAddr.add(0x30); -var patchReplyProtobufFunc2Byte; -var replyProtobufDeleteAddr = replyCallbackFuncAddr.add(0x6c); -var replyProtobufDeleteAddrByte; -var replyMessageCallbackFunc = baseAddr.add({{.replyMessageCallbackFunc}}); +var replyMessageCallbackFunc; var replyCgiAddr = ptr(0); var sendReplyMessageAddr = ptr(0); var replyMessageAddr = ptr(0); -var replyProtoX1PayloadAddr = ptr(0); // -------------------------全局变量分区------------------------- @@ -163,11 +284,10 @@ var replyProtoX1PayloadAddr = ptr(0); function setupSendTextMessageDynamic() { // 动态分配内存 - // 1. 动态分配内存块(按需分配大小) - // 分配原则:字符串给 64-128 字节,结构体按实际大小分配 textCgiAddr = Memory.alloc(128); sendTextMessageAddr = Memory.alloc(256); textMessageAddr = Memory.alloc(256); + textProtoDataAddr = Memory.alloc(4096); // A. 写入字符串内容 patchString(textCgiAddr, "/cgi-bin/micromsg-bin/newsendmsg"); @@ -178,113 +298,160 @@ function setupSendTextMessageDynamic() { sendTextMessageAddr.add(0x10).writeU64(0); sendTextMessageAddr.add(0x18).writeU64(1); sendTextMessageAddr.add(0x20).writeU32(taskIdGlobal); - sendTextMessageAddr.add(0x28).writePointer(textMessageAddr); // 指向动态分配的 Message - - // console.log(" [+] sendTextMessageAddr Object: ", hexdump(sendTextMessageAddr, { - // offset: 0, - // length: 48, - // header: true, - // ansi: true - // })); + sendTextMessageAddr.add(0x28).writePointer(textMessageAddr); // C. 构建 Message 结构体 - textMessageAddr.add(0x00).writePointer(sendMessageCallbackFunc); + textMessageAddr.add(0x00).writePointer(fakeVtable); textMessageAddr.add(0x08).writeU32(taskIdGlobal); textMessageAddr.add(0x0c).writeU32(0x20a); textMessageAddr.add(0x10).writeU64(0x3); textMessageAddr.add(0x18).writePointer(textCgiAddr); textMessageAddr.add(0x20).writeU64(uint64("0x20")); - // console.log(" [+] textMessageAddr Object: ", hexdump(textMessageAddr, { - // offset: 0, - // length: 64, - // header: true, - // ansi: true - // })); - - patchTextProtobufByte = patchTextProtobufAddr.readByteArray(4); - patchTextProtobufDeleteByte = patchTextProtobufDeleteAddr.readByteArray(4); + console.log("[+] Dynamic Text Message Setup Complete."); } -setImmediate(setupSendTextMessageDynamic); + +// -------------------------发送文件消息分区------------------------- +function setupSendFileMessageDynamic() { + fileCgiAddr = Memory.alloc(128); + sendFileMessageAddr = Memory.alloc(256); + fileMessageAddr = Memory.alloc(256); + uploadFileIdAddr = Memory.alloc(128); + uploadFileX1 = Memory.alloc(1024); + patchString(uploadFileIdAddr, "file_upload_not_init"); + + patchString(fileCgiAddr, "/cgi-bin/micromsg-bin/sendappmsg"); + + sendFileMessageAddr.add(0x00).writeU64(0); + sendFileMessageAddr.add(0x08).writeU64(0); + sendFileMessageAddr.add(0x10).writeU64(0); + sendFileMessageAddr.add(0x18).writeU64(1); + sendFileMessageAddr.add(0x20).writeU32(taskIdGlobal); + sendFileMessageAddr.add(0x28).writePointer(fileMessageAddr); + + fileMessageAddr.add(0x00).writePointer(fakeVtable); + fileMessageAddr.add(0x08).writeU32(taskIdGlobal); + fileMessageAddr.add(0x0c).writeU32(0x6e); + fileMessageAddr.add(0x10).writeU64(0x3); + fileMessageAddr.add(0x18).writePointer(fileCgiAddr); + fileMessageAddr.add(0x20).writeU64(0x20); + fileMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030")); + fileMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100")); +} + +function triggerSendFileMessage(taskId, sender, receiver, protoHex, payloadHex) { + return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "file"); +} + +function triggerUploadFile(receiver, md5, filePath, payloadHex) { + return fillUploadX1AndStart(uploadFileIdAddr, ImagePathAddr1, uploadFileX1, receiver, md5, filePath, payloadHex); +} + +// -------------------------sendfileuploadmsg分区------------------------- +function setupSendFileUploadMessageDynamic() { + fileUploadCgiAddr = Memory.alloc(128); + sendFileUploadMessageAddr = Memory.alloc(256); + fileUploadMessageAddr = Memory.alloc(256); + + patchString(fileUploadCgiAddr, "/cgi-bin/micromsg-bin/sendfileuploadmsg"); + + sendFileUploadMessageAddr.add(0x00).writeU64(0); + sendFileUploadMessageAddr.add(0x08).writeU64(0); + sendFileUploadMessageAddr.add(0x10).writeU64(0); + sendFileUploadMessageAddr.add(0x18).writeU64(1); + sendFileUploadMessageAddr.add(0x20).writeU32(taskIdGlobal); + sendFileUploadMessageAddr.add(0x28).writePointer(fileUploadMessageAddr); + + fileUploadMessageAddr.add(0x00).writePointer(fakeVtable); + fileUploadMessageAddr.add(0x08).writeU32(taskIdGlobal); + fileUploadMessageAddr.add(0x0c).writeU32(0x6e); + fileUploadMessageAddr.add(0x10).writeU64(0x3); + fileUploadMessageAddr.add(0x18).writePointer(fileUploadCgiAddr); + fileUploadMessageAddr.add(0x20).writeU64(0x20); + fileUploadMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030")); + fileUploadMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100")); +} + +function triggerSendFileUploadMessage(taskId, sender, receiver, protoHex, payloadHex) { + return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "fileupload"); +} + +// -------------------------发送文件消息分区------------------------- -function patchTextProtoBuf() { - Interceptor.attach(textCallbackFuncAddr, { - onEnter: function (args) { - var firstValue = this.context.sp.readU32(); - if (firstValue === taskIdGlobal) { - if (patchTextProtobufAddr.readU32() !== 3573751839) { - Memory.patchCode(patchTextProtobufAddr, 4, code => { - const cw = new Arm64Writer(code, {pc: patchTextProtobufAddr}); - cw.putNop(); - cw.flush(); - }); - Memory.patchCode(patchTextProtobufDeleteAddr, 4, code => { - const cw = new Arm64Writer(code, {pc: patchTextProtobufDeleteAddr}); - cw.putNop(); - cw.flush(); - }); - } - } else { - if (patchTextProtobufAddr.readU32() === 3573751839) { - Memory.patchCode(patchTextProtobufAddr, 4, code => { - const cw = new Arm64Writer(code, {pc: patchTextProtobufAddr}); - cw.putBytes(new Uint8Array(patchTextProtobufByte)); - cw.flush(); - }); - Memory.patchCode(patchTextProtobufDeleteAddr, 4, code => { - const cw = new Arm64Writer(code, {pc: patchTextProtobufDeleteAddr}); - cw.putBytes(new Uint8Array(patchTextProtobufDeleteByte)); - cw.flush(); - }); - } +// 创建一个只返回1的小函数stub +function setupRetOneStub() { + retOneStub = Memory.alloc(Process.pageSize); + Memory.patchCode(retOneStub, 8, code => { + // MOV W0, #1 = 0x52800020, RET = 0xD65F03C0 (little-endian) + code.writeByteArray([0x20, 0x00, 0x80, 0x52, 0xC0, 0x03, 0x5F, 0xD6]); + }); + console.log("[+] Return-1 stub created at: " + retOneStub); + // 构造假vtable:所有槽位指向retOneStub,这样mars对我们伪造结构做虚调用时不会崩溃 + fakeVtable = Memory.alloc(512); + for (var i = 0; i < 64; i++) { + fakeVtable.add(i * 8).writePointer(retOneStub); + } + console.log("[+] Fake vtable created at: " + fakeVtable); +} + +function attachBlrX8Hook() { + console.log("[+] Hooking BLR X8 at: " + blrX8Addr); + + var nativeAutoBufferWrite = new NativeFunction(autoBufferWriteFunc, 'int', ['pointer', 'pointer', 'int']); + + Interceptor.attach(blrX8Addr, { + onEnter: function(args) { + var currentTaskId = this.context.x20.toUInt32(); + if (currentTaskId !== taskIdGlobal) { + return; } - } - }) + console.log("[+] BLR X8 命中! taskId=" + currentTaskId + " sendMsgType=" + sendMsgType); + + var autoBuffer = this.context.x1; + var protoHex = ""; + + if (sendMsgType === "text") { + protoHex = textProtoHexGlobal; + } else if (sendMsgType === "img") { + protoHex = imgProtoHexGlobal; + } else if (sendMsgType === "video") { + protoHex = videoProtoHexGlobal; + } else if (sendMsgType === "reply") { + protoHex = replyProtoHexGlobal; + } else if (sendMsgType === "file") { + protoHex = fileProtoHexGlobal; + } else if (sendMsgType === "fileupload") { + protoHex = fileUploadProtoHexGlobal; + } else if (sendMsgType === "voice") { + protoHex = voiceProtoHexGlobal; + } + + if (!protoHex || protoHex.length === 0) { + console.error("[!] protoHex 为空, sendMsgType=" + sendMsgType); + return; + } + + var finalPayload = hexToByteArray(protoHex); + textProtoDataAddr.writeByteArray(finalPayload); + + // 调用 autoBufferWrite(autoBuffer, data, len) 填充 v133 + nativeAutoBufferWrite(autoBuffer, textProtoDataAddr, finalPayload.length); + console.log("[+] autoBufferWrite 调用完成, protobuf长度: " + finalPayload.length); + + // 将 X8 指向 retOneStub,这样 BLR X8 只会返回1,不执行原始逻辑 + this.context.x8 = retOneStub; + } + }); } -setImmediate(patchTextProtoBuf); function triggerSendTextMessage(taskId, receiver, content, atUser, protoHex, payloadHex) { - // console.log("[+] Manual Trigger Started..."); - if (!taskId || !receiver || !content) { - console.error("[!] taskId or Receiver or Content is empty!"); - return "fail"; - } - - if (!triggerX0 || !triggerX1Payload) { - console.error("[!] triggerX0 或 triggerX1Payload 尚未初始化,请等待 hook 捕获"); - return "fail"; - } - - taskIdGlobal = taskId; - textProtoHexGlobal = protoHex; - - textMessageAddr.add(0x08).writeU32(taskIdGlobal); - sendTextMessageAddr.add(0x20).writeU32(taskIdGlobal); - - // 使用Go传过来的payloadHex - const payloadData = hexToByteArray(payloadHex); - triggerX1Payload.writeByteArray(payloadData); - triggerX1Payload.add(0x18).writePointer(textCgiAddr); - triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0)); - triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198)); - sendMsgType = "text" - - const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']); - - // 5. 调用函数 - try { - MMStartTask(triggerX0, triggerX1Payload); - return "1"; - } catch (e) { - console.error(`[!] Error trigger MMStartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}), during execution: ` + e); - return "fail"; - } + return triggerSendMediaMessage(taskId, "", receiver, protoHex, payloadHex, "text"); } function AttachSendFunc() { @@ -302,38 +469,6 @@ function AttachSendFunc() { }) } -setImmediate(AttachSendFunc); - -// 拦截 SendTextProto 编码逻辑,注入自定义 Payload -function attachSendTextProto() { - textProtoX1PayloadAddr = Memory.alloc(3096); - console.log("[+] Frida Payload 地址: " + textProtoX1PayloadAddr); - - Interceptor.attach(protobufAddr, { - onEnter: function (args) { - - var sp = this.context.sp; - var firstValue = sp.readU32(); - if (firstValue !== taskIdGlobal) { - console.log("[+] Protobuf 拦截未命中,跳过..."); - return; - } - - // 使用Go传入的protobuf数据 - if (!textProtoHexGlobal || textProtoHexGlobal.length === 0) { - console.error("[!] textProtoHexGlobal 为空"); - return; - } - - const finalPayload = hexToByteArray(textProtoHexGlobal); - textProtoX1PayloadAddr.writeByteArray(finalPayload); - this.context.x1 = textProtoX1PayloadAddr; - this.context.x2 = ptr(finalPayload.length); - }, - }); -} - -setImmediate(attachSendTextProto); // -------------------------发送文本消息分区------------------------- @@ -365,26 +500,38 @@ function attachReq2buf() { insertMsgAddr.writePointer(sendReplyMessageAddr); console.log("[+] 发送回复消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendReplyMessageAddr + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); + } else if (sendMsgType === "voice") { + insertMsgAddr.writePointer(sendVoiceMessageAddr); + console.log("[+] 发送语音消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendVoiceMessageAddr + + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); + } else if (sendMsgType === "file") { + insertMsgAddr.writePointer(sendFileMessageAddr); + console.log("[+] 发送文件消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendFileMessageAddr + + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); + } else if (sendMsgType === "fileupload") { + insertMsgAddr.writePointer(sendFileUploadMessageAddr); + console.log("[+] 发送fileUploadMsg成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendFileUploadMessageAddr + + "[+] Req2Buf 写入后内存预览: " + insertMsgAddr); } } }); - // 在出口处拦截req2buf,把insertMsgAddr设置为0,避免被垃圾回收导致整个程序崩溃 + // 在出口处拦截req2buf,记录insertMsgAddr等buf2resp回调后再清理 Interceptor.attach(req2bufExitAddr, { onEnter: function (args) { if (!this.context.x25.equals(taskIdGlobal)) { return; } - insertMsgAddr.writeU64(0x0); + // 不立即清除insertMsgAddr,让mars能路由buf2resp回调 + // 用fakeVtable保护结构体,防止中间被访问时崩溃 + pendingInsertMsgAddr = insertMsgAddr; + pendingSendMsgType = sendMsgType; + pendingBuf2RespTaskId = taskIdGlobal; taskIdGlobal = 0; - send({ - type: "finish", - }) } }); } -setImmediate(attachReq2buf); // -------------------------Req2Buf公共部分分区------------------------- @@ -406,7 +553,6 @@ function setupSendImgMessageDynamic() { uploadAesKeyAddr = Memory.alloc(256); ImagePathAddr1 = Memory.alloc(256); uploadImageX1 = Memory.alloc(1024); - imgProtoX1PayloadAddr = Memory.alloc(2048); // 图片数据写入 patchString(imgCgiAddr, "/cgi-bin/micromsg-bin/uploadmsgimg"); @@ -418,7 +564,7 @@ function setupSendImgMessageDynamic() { sendImgMessageAddr.add(0x20).writeU32(taskIdGlobal); sendImgMessageAddr.add(0x28).writePointer(imgMessageAddr); - imgMessageAddr.add(0x00).writePointer(imgMessageCallbackFunc); + imgMessageAddr.add(0x00).writePointer(fakeVtable); imgMessageAddr.add(0x08).writeU32(taskIdGlobal); imgMessageAddr.add(0x0c).writeU32(0x6e); imgMessageAddr.add(0x10).writeU64(0x3); @@ -427,10 +573,6 @@ function setupSendImgMessageDynamic() { imgMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030")); imgMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100")); - patchImgProtobufFunc1Byte = patchImgProtobufFunc1.readByteArray(4); - patchImgProtobufFunc2Byte = patchImgProtobufFunc2.readByteArray(4); - imgProtobufDeleteAddrByte = imgProtobufDeleteAddr.readByteArray(4); - // 视频数据写入 videoCgiAddr = Memory.alloc(128); sendVideoMessageAddr = Memory.alloc(256); @@ -438,7 +580,6 @@ function setupSendImgMessageDynamic() { videoIdAddr = Memory.alloc(256); videoPathAddr1 = Memory.alloc(256); uploadVideoX1 = Memory.alloc(1024); - videoProtoX1PayloadAddr = Memory.alloc(2048); patchString(videoCgiAddr, "/cgi-bin/micromsg-bin/uploadvideo"); @@ -449,7 +590,7 @@ function setupSendImgMessageDynamic() { sendVideoMessageAddr.add(0x20).writeU32(taskIdGlobal); sendVideoMessageAddr.add(0x28).writePointer(videoMessageAddr); - videoMessageAddr.add(0x00).writePointer(videoMessageCallbackFunc); + videoMessageAddr.add(0x00).writePointer(fakeVtable); videoMessageAddr.add(0x08).writeU32(taskIdGlobal); videoMessageAddr.add(0x0c).writeU32(0x6e); videoMessageAddr.add(0x10).writeU64(0x3); @@ -458,113 +599,39 @@ function setupSendImgMessageDynamic() { videoMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030")); videoMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100")); - patchVideoProtobufFunc1Byte = patchVideoProtobufFunc1.readByteArray(4); - patchVideoProtobufFunc2Byte = patchVideoProtobufFunc2.readByteArray(4); - videoProtobufDeleteAddrByte = videoProtobufDeleteAddr.readByteArray(4); + // 语音数据写入 + voiceCgiAddr = Memory.alloc(128); + sendVoiceMessageAddr = Memory.alloc(256); + voiceMessageAddr = Memory.alloc(256); + voiceIdAddr = Memory.alloc(256); + voicePathAddr1 = Memory.alloc(256); + uploadVoiceX1 = Memory.alloc(1024); + voiceAudioDataAddr = Memory.alloc(5 * 1024 * 1024); // 预分配5MB + + patchString(voiceCgiAddr, "/cgi-bin/micromsg-bin/uploadvoice"); + + sendVoiceMessageAddr.add(0x00).writeU64(0); + sendVoiceMessageAddr.add(0x08).writeU64(0); + sendVoiceMessageAddr.add(0x10).writeU64(0); + sendVoiceMessageAddr.add(0x18).writeU64(1); + sendVoiceMessageAddr.add(0x20).writeU32(taskIdGlobal); + sendVoiceMessageAddr.add(0x28).writePointer(voiceMessageAddr); + + voiceMessageAddr.add(0x00).writePointer(fakeVtable); + voiceMessageAddr.add(0x08).writeU32(taskIdGlobal); + voiceMessageAddr.add(0x0c).writeU32(0x6e); + voiceMessageAddr.add(0x10).writeU64(0x3); + voiceMessageAddr.add(0x18).writePointer(voiceCgiAddr); + voiceMessageAddr.add(0x20).writeU64(0x21); + voiceMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030")); + voiceMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100")); } -setImmediate(setupSendImgMessageDynamic); -function patchImgProtoBuf() { - Interceptor.attach(imageCallbackFuncAddr, { - onEnter: function (args) { - var firstValue = this.context.sp.add(0x10).readU32(); - if (firstValue === taskIdGlobal) { - if (patchImgProtobufFunc1.readU32() !== 3573751839) { - Memory.patchCode(patchImgProtobufFunc1, 4, code => { - const cw = new Arm64Writer(code, {pc: patchImgProtobufFunc1}); - cw.putNop(); - cw.flush(); - }); - Memory.patchCode(patchImgProtobufFunc2, 4, code => { - const cw = new Arm64Writer(code, {pc: patchImgProtobufFunc2}); - cw.putNop(); - cw.flush(); - }); - Memory.patchCode(imgProtobufDeleteAddr, 4, code => { - const cw = new Arm64Writer(code, {pc: imgProtobufDeleteAddr}); - cw.putNop(); - cw.flush(); - }); - } - } else { - if (patchImgProtobufFunc1.readU32() === 3573751839) { - Memory.patchCode(patchImgProtobufFunc1, 4, code => { - const cw = new Arm64Writer(code, {pc: patchImgProtobufFunc1}); - cw.putBytes(new Uint8Array(patchImgProtobufFunc1Byte)); - cw.flush(); - }); - Memory.patchCode(patchImgProtobufFunc2, 4, code => { - const cw = new Arm64Writer(code, {pc: patchImgProtobufFunc2}); - cw.putBytes(new Uint8Array(patchImgProtobufFunc2Byte)); - cw.flush(); - }); - Memory.patchCode(imgProtobufDeleteAddr, 4, code => { - const cw = new Arm64Writer(code, {pc: imgProtobufDeleteAddr}); - cw.putBytes(new Uint8Array(imgProtobufDeleteAddrByte)); - cw.flush(); - }); - } - - } - } - }) -} - -setImmediate(patchImgProtoBuf); - -function patchVideoProtoBuf() { - Interceptor.attach(videoCallbackFuncAddr, { - onEnter: function (args) { - var firstValue = this.context.sp.add(0x10).readU32(); - if (firstValue === taskIdGlobal) { - if (patchVideoProtobufFunc1.readU32() !== 3573751839) { - Memory.patchCode(patchVideoProtobufFunc1, 4, code => { - const cw = new Arm64Writer(code, {pc: patchVideoProtobufFunc1}); - cw.putNop(); - cw.flush(); - }); - Memory.patchCode(patchVideoProtobufFunc2, 4, code => { - const cw = new Arm64Writer(code, {pc: patchVideoProtobufFunc2}); - cw.putNop(); - cw.flush(); - }); - Memory.patchCode(videoProtobufDeleteAddr, 4, code => { - const cw = new Arm64Writer(code, {pc: videoProtobufDeleteAddr}); - cw.putNop(); - cw.flush(); - }); - } - } else { - if (patchVideoProtobufFunc1.readU32() === 3573751839) { - Memory.patchCode(patchVideoProtobufFunc1, 4, code => { - const cw = new Arm64Writer(code, {pc: patchVideoProtobufFunc1}); - cw.putBytes(new Uint8Array(patchVideoProtobufFunc1Byte)); - cw.flush(); - }); - Memory.patchCode(patchVideoProtobufFunc2, 4, code => { - const cw = new Arm64Writer(code, {pc: patchVideoProtobufFunc2}); - cw.putBytes(new Uint8Array(patchVideoProtobufFunc2Byte)); - cw.flush(); - }); - Memory.patchCode(videoProtobufDeleteAddr, 4, code => { - const cw = new Arm64Writer(code, {pc: videoProtobufDeleteAddr}); - cw.putBytes(new Uint8Array(videoProtobufDeleteAddrByte)); - cw.flush(); - }); - } - - } - } - }) -} - -setImmediate(patchVideoProtoBuf); - -function triggerSendImgMessage(taskId, sender, receiver, protoHex, payloadHex) { - if (!taskId || !receiver || !sender) { - console.error("[!] taskId or receiver or sender is empty!"); +function triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, msgType) { + if (!taskId || !receiver) { + console.error("[!] " + msgType + ": taskId or receiver is empty!"); return "fail"; } @@ -573,202 +640,107 @@ function triggerSendImgMessage(taskId, sender, receiver, protoHex, payloadHex) { return "fail"; } - // 保存protobuf hex供attachProto使用 - imgProtoHexGlobal = protoHex; + var msgAddrInfo = { + "text": { messageAddr: textMessageAddr, sendMessageAddr: sendTextMessageAddr, cgiAddr: textCgiAddr, protoHexSetter: function(h) { textProtoHexGlobal = h; } }, + "img": { messageAddr: imgMessageAddr, sendMessageAddr: sendImgMessageAddr, cgiAddr: imgCgiAddr, protoHexSetter: function(h) { imgProtoHexGlobal = h; } }, + "video": { messageAddr: videoMessageAddr, sendMessageAddr: sendVideoMessageAddr, cgiAddr: videoCgiAddr, protoHexSetter: function(h) { videoProtoHexGlobal = h; } }, + "reply": { messageAddr: replyMessageAddr, sendMessageAddr: sendReplyMessageAddr, cgiAddr: replyCgiAddr, protoHexSetter: function(h) { replyProtoHexGlobal = h; } }, + "voice": { messageAddr: voiceMessageAddr, sendMessageAddr: sendVoiceMessageAddr, cgiAddr: voiceCgiAddr, protoHexSetter: function(h) { voiceProtoHexGlobal = h; } }, + "file": { messageAddr: fileMessageAddr, sendMessageAddr: sendFileMessageAddr, cgiAddr: fileCgiAddr, protoHexSetter: function(h) { fileProtoHexGlobal = h; } }, + "fileupload": { messageAddr: fileUploadMessageAddr, sendMessageAddr: sendFileUploadMessageAddr, cgiAddr: fileUploadCgiAddr, protoHexSetter: function(h) { fileUploadProtoHexGlobal = h; } }, + }; + var info = msgAddrInfo[msgType]; + if (!info) { + console.error("[!] unknown msgType: " + msgType); + return "fail"; + } + + info.protoHexSetter(protoHex); taskIdGlobal = taskId; - imgMessageAddr.add(0x08).writeU32(taskIdGlobal); - sendImgMessageAddr.add(0x20).writeU32(taskIdGlobal); + info.messageAddr.add(0x08).writeU32(taskIdGlobal); + info.sendMessageAddr.add(0x20).writeU32(taskIdGlobal); - // 使用Go传过来的payloadHex const payloadData = hexToByteArray(payloadHex); triggerX1Payload.writeByteArray(payloadData); - triggerX1Payload.add(0x18).writePointer(imgCgiAddr); + triggerX1Payload.add(0x18).writePointer(info.cgiAddr); triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0)); triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198)); - sendMsgType = "img" + sendMsgType = msgType; const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']); - // 5. 调用函数 try { MMStartTask(triggerX0, triggerX1Payload); return "1"; } catch (e) { - console.error(`[!] Error trigger StartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}), during execution: ` + e); + console.error("[!] Error trigger " + msgType + " MMStartTask: " + e); return "fail"; } } +function triggerSendImgMessage(taskId, sender, receiver, protoHex, payloadHex) { + return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "img"); +} + function triggerSendVideoMessage(taskId, sender, receiver, protoHex, payloadHex) { - if (!taskId || !receiver || !sender) { - console.error("[!] taskId or receiver or sender is empty!"); - return "fail"; - } - - if (!triggerX0 || !triggerX1Payload) { - console.error("[!] triggerX0 或 triggerX1Payload 尚未初始化,请等待 hook 捕获"); - return "fail"; - } - - // 保存protobuf hex供attachProto使用 - videoProtoHexGlobal = protoHex; - - taskIdGlobal = taskId; - - videoMessageAddr.add(0x08).writeU32(taskIdGlobal); - sendVideoMessageAddr.add(0x20).writeU32(taskIdGlobal); - - // 使用Go传过来的payloadHex - const payloadData = hexToByteArray(payloadHex); - triggerX1Payload.writeByteArray(payloadData); - triggerX1Payload.add(0x18).writePointer(videoCgiAddr); - triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0)); - triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198)); - sendMsgType = "video" - - const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']); - - // 5. 调用函数 - try { - MMStartTask(triggerX0, triggerX1Payload); - return "1"; - } catch (e) { - console.error(`[!] Error trigger StartTask ${sendFuncAddr} with args: (${triggerX0}) (${triggerX1Payload}), during execution: ` + e); - return "fail"; - } + return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "video"); } -// 拦截 Protobuf 编码逻辑,注入自定义 Payload -function attachProto() { - Interceptor.attach(imgProtobufAddr, { - onEnter: function (args) { - var currTaskId = this.context.sp.add(0x30).readU32(); - if (currTaskId !== taskIdGlobal) { - console.log(`[+] 拦截到非目标 currTaskId: ${currTaskId} taskIdGlobal: ${taskIdGlobal}`); - return; - } - - if (!imgProtoHexGlobal || imgProtoHexGlobal.length === 0) { - console.error("[!] imgProtoHexGlobal 为空"); - return; - } - - const finalPayload = hexToByteArray(imgProtoHexGlobal); - imgProtoX1PayloadAddr.writeByteArray(finalPayload); - - this.context.x1 = imgProtoX1PayloadAddr; - this.context.x2 = ptr(finalPayload.length); - }, - }); - - Interceptor.attach(videoProtobufAddr, { - onEnter: function (args) { - - var currTaskId = this.context.sp.add(0x30).readU32(); - if (currTaskId !== taskIdGlobal) { - return; - } - - if (!videoProtoHexGlobal || videoProtoHexGlobal.length === 0) { - console.error("[!] videoProtoHexGlobal 为空"); - return; - } - - const finalPayload = hexToByteArray(videoProtoHexGlobal); - videoProtoX1PayloadAddr.writeByteArray(finalPayload); - - this.context.x1 = videoProtoX1PayloadAddr; - this.context.x2 = ptr(finalPayload.length); - }, - }); -} - -setImmediate(attachProto); - - function triggerUploadImg(receiver, md5, imagePath, payloadHex) { - if (uploadGlobalX0.equals(ptr(0))) { - console.error("[!] uploadGlobalX0 尚未初始化,请等待 hook 捕获"); - return "fail"; - } - - // 使用Go传过来的payloadHex - const payload = hexToByteArray(payloadHex); - - patchString(imageIdAddr, receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1"); - patchString(md5Addr, md5) - patchString(uploadAesKeyAddr, generateAESKey()) - patchString(ImagePathAddr1, imagePath); - - uploadImageX1.writeByteArray(payload); - uploadImageX1.writePointer(uploadFunc1Addr); - uploadImageX1.add(0x08).writePointer(uploadFunc2Addr); - uploadImageX1.add(0x48).writePointer(imageIdAddr); - uploadImageX1.add(0x68).writeUtf8String(receiver); - uploadImageX1.add(0xa8).writePointer(md5Addr); - uploadImageX1.add(0xe8).writePointer(ImagePathAddr1); - uploadImageX1.add(0x118).writePointer(ImagePathAddr1); - uploadImageX1.add(0x148).writePointer(ImagePathAddr1); - uploadImageX1.add(0x200).writePointer(uploadAesKeyAddr); - - const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']); - - console.log(`[+] triggerUploadImg X0: ${uploadGlobalX0}`); - return startUploadMedia(uploadGlobalX0, uploadImageX1); + return fillUploadX1AndStart(imageIdAddr, ImagePathAddr1, uploadImageX1, receiver, md5, imagePath, payloadHex); } function triggerUploadVideo(receiver, md5, videoPath, payloadHex) { + return fillUploadX1AndStart(videoIdAddr, videoPathAddr1, uploadVideoX1, receiver, md5, videoPath, payloadHex); +} + +function triggerUploadVoice(receiver, voicePath, payloadHex, audioDataHex, durationMs) { if (uploadGlobalX0.equals(ptr(0))) { console.error("[!] uploadGlobalX0 尚未初始化,请等待 hook 捕获"); return "fail"; } + voiceDurationGlobal = durationMs; const payload = hexToByteArray(payloadHex); - patchString(videoIdAddr, receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1"); - patchString(md5Addr, md5) - patchString(uploadAesKeyAddr, generateAESKey()) - patchString(videoPathAddr1, videoPath); + // 解码音频二进制数据,写入预分配的5MB内存 + const audioBytes = hexToByteArray(audioDataHex); + const audioLen = audioBytes.length; + voiceSilkDataLenGlobal = audioLen; + voiceAudioDataAddr.writeByteArray(audioBytes); - uploadVideoX1.writeByteArray(payload); - uploadVideoX1.writePointer(uploadFunc1Addr); - uploadVideoX1.add(0x08).writePointer(uploadFunc2Addr); - uploadVideoX1.add(0x48).writePointer(videoIdAddr); - uploadVideoX1.add(0x68).writeUtf8String(receiver); - uploadVideoX1.add(0xa8).writePointer(md5Addr); - uploadVideoX1.add(0xe8).writePointer(videoPathAddr1); - uploadVideoX1.add(0x118).writePointer(videoPathAddr1); - uploadVideoX1.add(0x148).writePointer(videoPathAddr1); - uploadVideoX1.add(0x200).writePointer(uploadAesKeyAddr); + const voiceIdStr = receiver + "_" + String(Math.floor(Date.now() / 1000)) + "_" + Math.floor(Math.random() * 1001) + "_1"; + patchString(voiceIdAddr, voiceIdStr); + patchString(voicePathAddr1, voicePath); + + uploadVoiceX1.writeByteArray(payload); + uploadVoiceX1.writePointer(uploadFunc1Addr); + uploadVoiceX1.add(0x08).writePointer(uploadFunc2Addr); + uploadVoiceX1.add(0x48).writePointer(voiceIdAddr); + uploadVoiceX1.add(0x50).writeU64(voiceIdStr.length); + uploadVoiceX1.add(0x58).writeU64(uint64("0x8000000000000000").add(voiceIdStr.length + 1)); + uploadVoiceX1.add(0x68).writeUtf8String(receiver); + // 音频二进制数据: 0x100=指针, 0x108=长度, 0x110=容量(长度+1)|高位 + uploadVoiceX1.add(0x100).writePointer(voiceAudioDataAddr); + uploadVoiceX1.add(0x108).writeU64(audioLen); + uploadVoiceX1.add(0x110).writeU64(uint64("0x8000000000000000").add(audioLen + 1)); const startUploadMedia = new NativeFunction(uploadImageAddr, 'int64', ['pointer', 'pointer']); - return startUploadMedia(uploadGlobalX0, uploadVideoX1); + return startUploadMedia(uploadGlobalX0, uploadVoiceX1); } function attachUploadMedia() { Interceptor.attach(uploadImageAddr.add(0x10), { onEnter: function (args) { - try { - uploadGlobalX0 = this.context.x0; - const selfId = this.context.x1.add(0x68).readUtf8String(); - const filePath = this.context.x1.add(0xe8).readPointer().readUtf8String(); - send({ - type: "upload", - self_id: selfId, - }) - } catch (e) { - console.error("[-] attachUploadMedia error: " + e); - uploadGlobalX0 = this.context.x0; - } - } + uploadGlobalX0 = this.context.x0; + } }) } -setImmediate(attachUploadMedia); function patchCdnOnComplete() { @@ -780,9 +752,11 @@ function patchCdnOnComplete() { const currentFileId = x2.add(0x20).readPointer().readUtf8String(); const imageFileId = imageIdAddr.readUtf8String(); const videoFileId = videoIdAddr.readUtf8String(); - if (currentFileId !== imageFileId && currentFileId !== videoFileId) { + const voiceFileId = voiceIdAddr.readUtf8String(); + const fileUploadFileId = uploadFileIdAddr.readUtf8String(); + if (currentFileId !== imageFileId && currentFileId !== videoFileId && currentFileId !== voiceFileId && currentFileId !== fileUploadFileId) { console.log("[-] CndOnComplete x2: " + x2 + " currentFileId: " + currentFileId + - " imageFileId: " + imageFileId + " videoFileId:" + videoFileId); + " imageFileId: " + imageFileId + " videoFileId:" + videoFileId + " voiceFileId:" + voiceFileId + " fileUploadFileId:" + fileUploadFileId); return; } @@ -792,15 +766,35 @@ function patchCdnOnComplete() { const videoId = x2.add(0xf0).readPointer().readUtf8String(); const targetId = x2.add(0x40).readUtf8String(); - send({ - type: "finish", - }); + console.log("cndOnComplete x2: " + x2 + " cdnKey: " + cdnKey + " aesKey: " + aesKey + " md5Key: " + md5Key + " videoId: " + videoId + " targetId: " + targetId); - if (cdnKey !== "" && cdnKey != null && aesKey !== "" && aesKey != null && - md5Key !== "" && md5Key != null) { + if (cdnKey !== "" && cdnKey != null && aesKey !== "" && aesKey != null) { - // 判断是图片还是视频,存入对应队列 - if (videoId !== null && videoId !== "") { + // 判断是语音、视频、文件还是图片 + if (currentFileId === voiceFileId) { + // 语音 + send({ + type: "upload_voice_finish", + target_id: targetId, + cdn_key: cdnKey, + aes_key: aesKey, + voice_duration: voiceDurationGlobal, + silk_data_len: voiceSilkDataLenGlobal + }); + } else if (currentFileId === fileUploadFileId) { + // 文件 + var attachId = "@cdn_" + cdnKey + "_" + aesKey + "_1"; + send({ + type: "upload_file_finish", + target_id: targetId, + cdn_key: cdnKey, + aes_key: aesKey, + md5_key: md5Key, + attach_id: attachId, + file_upload_token: "", + overwrite_msg_id: "" + }); + } else if (currentFileId === videoFileId) { // 视频 send({ type: "upload_video_finish", @@ -821,7 +815,7 @@ function patchCdnOnComplete() { }); } } else { - console.error("cdnKey or aesKey or md5key 为空"); + console.error("cdnKey or aesKey 为空"); } } catch (e) { console.error("[-] CdnOnComplete error: " + e); @@ -830,7 +824,6 @@ function patchCdnOnComplete() { }); } -setImmediate(patchCdnOnComplete) function attachGetCallbackFromWrapper() { Interceptor.attach(uploadGetCallbackWrapperAddr, { @@ -838,9 +831,11 @@ function attachGetCallbackFromWrapper() { try { const tmpFileId = this.context.x1.readPointer().readUtf8String(); const imageFileId = imageIdAddr.readUtf8String(); - const videoFileId = videoIdAddr.readUtf8String() - if (tmpFileId !== imageFileId && tmpFileId !== videoFileId) { - console.log("[+] GetCallbackFromWrapper tmpFileId: " + tmpFileId + " imageFileId: " + imageFileId + " videoFileId:" + videoFileId); + const videoFileId = videoIdAddr.readUtf8String(); + const voiceFileId = voiceIdAddr.readUtf8String(); + const fileUploadFileId = uploadFileIdAddr.readUtf8String(); + if (tmpFileId !== imageFileId && tmpFileId !== videoFileId && tmpFileId !== voiceFileId && tmpFileId !== fileUploadFileId) { + console.log("[+] GetCallbackFromWrapper tmpFileId: " + tmpFileId + " imageFileId: " + imageFileId + " videoFileId:" + videoFileId + " voiceFileId:" + voiceFileId + " fileUploadFileId:" + fileUploadFileId); return } @@ -857,9 +852,11 @@ function attachGetCallbackFromWrapper() { try { const tmpFileId = this.context.x1.readPointer().readUtf8String(); const imageFileId = imageIdAddr.readUtf8String(); - const videoFileId = videoIdAddr.readUtf8String() - if (tmpFileId !== imageFileId && tmpFileId !== videoFileId) { - console.log("[+] OnComplete tmpFileId: " + tmpFileId + " imageFileId: " + imageFileId + " videoFileId:" + videoFileId); + const videoFileId = videoIdAddr.readUtf8String(); + const voiceFileId = voiceIdAddr.readUtf8String(); + const fileUploadFileId = uploadFileIdAddr.readUtf8String(); + if (tmpFileId !== imageFileId && tmpFileId !== videoFileId && tmpFileId !== voiceFileId && tmpFileId !== fileUploadFileId) { + console.log("[+] OnComplete tmpFileId: " + tmpFileId + " imageFileId: " + imageFileId + " videoFileId:" + videoFileId + " voiceFileId:" + voiceFileId + " fileUploadFileId:" + fileUploadFileId); return } @@ -872,14 +869,12 @@ function attachGetCallbackFromWrapper() { }) } -setImmediate(attachGetCallbackFromWrapper); // -------------------------发送回复消息分区------------------------- function setupSendReplyMessageDynamic() { replyCgiAddr = Memory.alloc(128); sendReplyMessageAddr = Memory.alloc(256); replyMessageAddr = Memory.alloc(256); - replyProtoX1PayloadAddr = Memory.alloc(4096); patchString(replyCgiAddr, "/cgi-bin/micromsg-bin/sendappmsg"); @@ -890,132 +885,31 @@ function setupSendReplyMessageDynamic() { sendReplyMessageAddr.add(0x20).writeU32(taskIdGlobal); sendReplyMessageAddr.add(0x28).writePointer(replyMessageAddr); - replyMessageAddr.add(0x00).writePointer(replyMessageCallbackFunc); + replyMessageAddr.add(0x00).writePointer(fakeVtable); replyMessageAddr.add(0x08).writeU32(taskIdGlobal); replyMessageAddr.add(0x0c).writeU32(0x6e); replyMessageAddr.add(0x10).writeU64(0x3); replyMessageAddr.add(0x18).writePointer(replyCgiAddr); - replyMessageAddr.add(0x20).writeU64(0x22); + replyMessageAddr.add(0x20).writeU64(0x20); replyMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030")); replyMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100")); - patchReplyProtobufFunc1Byte = patchReplyProtobufFunc1.readByteArray(4); - patchReplyProtobufFunc2Byte = patchReplyProtobufFunc2.readByteArray(4); - replyProtobufDeleteAddrByte = replyProtobufDeleteAddr.readByteArray(4); - console.log("[+] Reply message setup complete. CgiAddr: " + replyCgiAddr + " SendAddr: " + sendReplyMessageAddr); } -setImmediate(setupSendReplyMessageDynamic); - -function patchReplyProtoBuf() { - Interceptor.attach(replyCallbackFuncAddr, { - onEnter: function (args) { - var firstValue = this.context.sp.add(0x10).readU32(); - if (firstValue === taskIdGlobal) { - if (patchReplyProtobufFunc1.readU32() !== 3573751839) { - Memory.patchCode(patchReplyProtobufFunc1, 4, code => { - const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc1}); - cw.putNop(); - cw.flush(); - }); - Memory.patchCode(patchReplyProtobufFunc2, 4, code => { - const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc2}); - cw.putNop(); - cw.flush(); - }); - Memory.patchCode(replyProtobufDeleteAddr, 4, code => { - const cw = new Arm64Writer(code, {pc: replyProtobufDeleteAddr}); - cw.putNop(); - cw.flush(); - }); - } - } else { - if (patchReplyProtobufFunc1.readU32() === 3573751839) { - Memory.patchCode(patchReplyProtobufFunc1, 4, code => { - const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc1}); - cw.putBytes(new Uint8Array(patchReplyProtobufFunc1Byte)); - cw.flush(); - }); - Memory.patchCode(patchReplyProtobufFunc2, 4, code => { - const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc2}); - cw.putBytes(new Uint8Array(patchReplyProtobufFunc2Byte)); - cw.flush(); - }); - Memory.patchCode(replyProtobufDeleteAddr, 4, code => { - const cw = new Arm64Writer(code, {pc: replyProtobufDeleteAddr}); - cw.putBytes(new Uint8Array(replyProtobufDeleteAddrByte)); - cw.flush(); - }); - } - } - } - }) -} - -setImmediate(patchReplyProtoBuf); - -function attachReplyProto() { - Interceptor.attach(replyProtobufAddr, { - onEnter: function (args) { - var currTaskId = this.context.sp.add(0x30).readU32(); - if (currTaskId !== taskIdGlobal) { - return; - } - - if (!replyProtoHexGlobal || replyProtoHexGlobal.length === 0) { - console.error("[!] replyProtoHexGlobal 为空"); - return; - } - - const finalPayload = hexToByteArray(replyProtoHexGlobal); - replyProtoX1PayloadAddr.writeByteArray(finalPayload); - this.context.x1 = replyProtoX1PayloadAddr; - this.context.x2 = ptr(finalPayload.length); - console.log("[+] Reply protobuf注入完成, length=" + finalPayload.length); - }, - }); -} - -setImmediate(attachReplyProto); function triggerSendReplyMessage(taskId, sender, receiver, protoHex, payloadHex) { - if (!taskId || !receiver || !sender) { - console.error("[!] reply: taskId or receiver or sender is empty!"); - return "fail"; - } - - if (!triggerX0 || !triggerX1Payload) { - console.error("[!] triggerX0 或 triggerX1Payload 尚未初始化,请等待 hook 捕获"); - return "fail"; - } - - replyProtoHexGlobal = protoHex; - taskIdGlobal = taskId; - - replyMessageAddr.add(0x08).writeU32(taskIdGlobal); - sendReplyMessageAddr.add(0x20).writeU32(taskIdGlobal); - - const payloadData = hexToByteArray(payloadHex); - triggerX1Payload.writeByteArray(payloadData); - triggerX1Payload.add(0x18).writePointer(replyCgiAddr); - triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0)); - triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198)); - sendMsgType = "reply" - - const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']); - - try { - MMStartTask(triggerX0, triggerX1Payload); - return "1"; - } catch (e) { - console.error("[!] Error trigger reply MMStartTask: " + e); - return "fail"; - } + return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "reply"); } // -------------------------发送回复消息分区------------------------- +// -------------------------发送语音消息分区------------------------- +function triggerSendVoiceMessage(taskId, sender, receiver, protoHex, payloadHex) { + return triggerSendMediaMessage(taskId, sender, receiver, protoHex, payloadHex, "voice"); +} +// -------------------------发送语音消息分区------------------------- + rpc.exports = { triggerSendImgMessage: triggerSendImgMessage, triggerUploadImg: triggerUploadImg, @@ -1024,6 +918,11 @@ rpc.exports = { triggerUploadVideo: triggerUploadVideo, triggerSendVideoMessage: triggerSendVideoMessage, triggerSendReplyMessage: triggerSendReplyMessage, + triggerUploadVoice: triggerUploadVoice, + triggerSendVoiceMessage: triggerSendVoiceMessage, + triggerSendFileMessage: triggerSendFileMessage, + triggerSendFileUploadMessage: triggerSendFileUploadMessage, + triggerUploadFile: triggerUploadFile, }; // -------------------------发送图片消息分区------------------------- @@ -1032,38 +931,51 @@ rpc.exports = { function setupDownloadFileDynamic() { downloadFileX1 = Memory.alloc(1624) fileIdAddr = Memory.alloc(128) - fileMd5Addr = Memory.alloc(128) downloadAesKeyAddr = Memory.alloc(128) filePathAddr = Memory.alloc(256) fileCdnUrlAddr = Memory.alloc(256) } -setImmediate(setupDownloadFileDynamic) function setReceiver() { Interceptor.attach(buf2RespAddr, { onEnter: function (args) { - const currentPtr = this.context.x20; - if (currentPtr.add(0).readU8() !== 0x08) { - return + // 通过 SP+0x140 读取当前 buf2resp 对应的 taskId + var respTaskId = this.context.sp.add(0x140).readS32(); + const currentPtr = this.context.x20; + const x2 = this.context.x0.toInt32(); + + // 判断是否是我们发送的消息的 ack + if (pendingBuf2RespTaskId !== 0 && respTaskId === pendingBuf2RespTaskId) { + // 清理 insertMsgAddr + if (!pendingInsertMsgAddr.isNull()) { + pendingInsertMsgAddr.writeU64(0x0); + console.log("[+] buf2resp: 已清理 insertMsgAddr, msgType=" + pendingSendMsgType + " taskId=" + respTaskId); + pendingInsertMsgAddr = ptr(0); + } + + // 读取响应数据 + var respData = currentPtr.readByteArray(x2); + if (respData) { + var bytes = new Uint8Array(respData); + console.log("[+] buf2resp: 收到响应, msgType=" + pendingSendMsgType + " taskId=" + respTaskId + " len=" + x2); + send({ + type: "buf2resp", + msg_type: pendingSendMsgType, + data: Array.from(bytes), + }); + } + + pendingBuf2RespTaskId = 0; + pendingSendMsgType = ""; + return; } - const x2 = this.context.x0.toInt32(); - // console.log(" [+] currentPtr: ", hexdump(currentPtr, { - // offset: 0, - // length: x2, - // header: true, - // ansi: true - // })); - - // 过滤非聊天消息: - // 1. field 2 是 varint (tag=0x10) 而非嵌套 message (tag=0x12) → 同步/通知消息 // 2. field 2 wrapper长度 < 128 (单字节varint) → wrapper内容过小,非聊天消息 if (currentPtr.add(2).readU8() === 0x10 || currentPtr.add(2).readU8() === 0x16 || (currentPtr.add(3).readU8() & 0x80) === 0) { return; } - const mem = currentPtr.readByteArray(x2); if (!mem) return; const uint8Array = new Uint8Array(mem); @@ -1156,7 +1068,6 @@ function setReceiver() { }); } -setImmediate(setReceiver) // fileType: HdImage => 1,Image => 2, thumbImage => 3, Video => 4, File => 5, function triggerDownload(receiver, cdnUrl, aesKey, filePath, fileType) { diff --git a/onebot/text_builder.go b/onebot/text_builder.go index c82d5f8..5dc1500 100644 --- a/onebot/text_builder.go +++ b/onebot/text_builder.go @@ -7,7 +7,7 @@ import ( "math/rand" "time" - "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto" + wxproto "github.com/yincongcyincong/weixin-macos/onebot/proto" "google.golang.org/protobuf/proto" ) @@ -18,7 +18,7 @@ func BuildTextMsgProto(receiver, content, atUser string) (string, error) { if atUser != "" { xmlStr += "" + atUser + "" } - xmlStr += "1\x00" + xmlStr += "1" // 生成随机消息ID msgId := rand.Int63n(1<<34) | (1 << 34) @@ -40,6 +40,8 @@ func BuildTextMsgProto(receiver, content, atUser string) (string, error) { return "", fmt.Errorf("marshal text proto failed: %w", err) } + //fmt.Printf("[text-proto] final protobuf hex dump:\n%s\n", HexDump(data, 0)) + return hex.EncodeToString(data), nil } @@ -127,7 +129,22 @@ func BuildSendPayload(taskId int64, msgType string) string { case "reply": payloadData[0] = 0x6E payloadData[16] = 0x10 - payloadData[28] = 0x22 + payloadData[28] = 0x20 + payloadData[92] = 0x6E + case "file": + payloadData[0] = 0x6E + payloadData[16] = 0x10 + payloadData[28] = 0x20 + payloadData[92] = 0x6E + case "fileupload": + payloadData[0] = 0x6E + payloadData[16] = 0x10 + payloadData[28] = 0x27 + payloadData[92] = 0x6E + case "voice": + payloadData[0] = 0x6E + payloadData[16] = 0x10 + payloadData[28] = 0x21 payloadData[92] = 0x6E } diff --git a/onebot/upload_builder.go b/onebot/upload_builder.go index 53c447f..7aae39d 100644 --- a/onebot/upload_builder.go +++ b/onebot/upload_builder.go @@ -1,13 +1,15 @@ package main -import "encoding/hex" +import ( + "encoding/hex" +) -// BuildUploadPayload 构建上传图片/视频的payload模板,返回hex编码字符串 -// uploadType: "img", "video" +// BuildUploadPayload 构建上传图片/视频/语音的payload模板,返回hex编码字符串 +// uploadType: "img", "video", "voice" // JS侧会在固定偏移写入运行时指针: 0x00, 0x08, 0x48, 0x68(receiver), 0xa8, 0xe8, 0x118, 0x148, 0x200 func BuildUploadPayload(uploadType string) string { // 公共部分 (760字节 = 0x2F8),以img为基准 - // 差异字节: [0x9C] img=0x01/video=0x04, [0x1BC-0x1BF] img=AAAAAAAA/video=01000000 + // 差异字节: [0x9C] img=0x01/video=0x04/voice=0x0F, [0x1BC-0x1BF] img=AAAAAAAA/video=01000000/voice=07000000 payload := []byte{ 0x20, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0x00 (被uploadFunc1Addr覆盖) 0x00, 0x05, 0x33, 0x8C, 0x0B, 0x00, 0x00, 0x00, // 0x08 (被uploadFunc2Addr覆盖) @@ -18,7 +20,7 @@ func BuildUploadPayload(uploadType string) string { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x30 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x38 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 0x40 - 0xD0, 0x72, 0x20, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x48 (被imageIdAddr/videoIdAddr覆盖) + 0xD0, 0x72, 0x20, 0x89, 0x0B, 0x00, 0x00, 0x00, // 0x48 (被imageIdAddr/videoIdAddr/voiceIdAddr覆盖) 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x50 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // 0x58 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60 diff --git a/onebot/utils.go b/onebot/utils.go index 1644022..8c47af9 100644 --- a/onebot/utils.go +++ b/onebot/utils.go @@ -98,6 +98,15 @@ func GetFileMD5(filePath string) (string, error) { return hex.EncodeToString(hash.Sum(nil)), nil } +// FileToBase64 读取文件并返回 base64 编码字符串 +func FileToBase64(filePath string) (string, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return "", fmt.Errorf("read file failed: %v", err) + } + return base64.StdEncoding.EncodeToString(data), nil +} + func SaveAudioFile(silkBytes []byte) (path string, err error) { mp3Bytes, err := SilkToMp3(silkBytes) if err != nil { @@ -222,6 +231,22 @@ func DetectFileFormat(data []byte) string { case bytes.HasPrefix(data, []byte("7z\xBC\xAF\x27\x1C")): return "7z" + // 音频格式 + case data[0] == 0x02 && bytes.HasPrefix(data[1:], []byte("#!SILK")): + return "silk" + case bytes.HasPrefix(data, []byte("#!SILK")): + return "silk" + case bytes.HasPrefix(data, []byte("RIFF")) && len(data) > 8 && bytes.HasPrefix(data[8:], []byte("WAVE")): + return "wav" + case bytes.HasPrefix(data, []byte{0xFF, 0xFB}) || bytes.HasPrefix(data, []byte{0xFF, 0xF3}) || bytes.HasPrefix(data, []byte{0xFF, 0xF2}): + return "mp3" + case bytes.HasPrefix(data, []byte("ID3")): + return "mp3" + case bytes.HasPrefix(data, []byte("OggS")): + return "ogg" + case bytes.HasPrefix(data, []byte("fLaC")): + return "flac" + default: return "unknown" } @@ -419,3 +444,40 @@ func cleanAndReattach() { // 重新等待微信进程并 attach attachWechat() } + +// HexDump formats data like: +// 0000000C12334C00 0A 40 0A 01 00 10 C6 BC 90 B9 08 1A 10 6D 36 34 .@....Ƽ.....m64 +func HexDump(data []byte, baseAddr uint64) string { + var sb strings.Builder + for i := 0; i < len(data); i += 16 { + // Address + sb.WriteString(fmt.Sprintf("%016X ", baseAddr+uint64(i))) + + // Hex bytes + for j := 0; j < 16; j++ { + if j == 8 { + sb.WriteByte(' ') + } + if i+j < len(data) { + sb.WriteString(fmt.Sprintf("%02X ", data[i+j])) + } else { + sb.WriteString(" ") + } + } + + // ASCII + sb.WriteByte(' ') + for j := 0; j < 16; j++ { + if i+j < len(data) { + b := data[i+j] + if b >= 0x20 && b <= 0x7E { + sb.WriteByte(b) + } else { + sb.WriteByte('.') + } + } + } + sb.WriteByte('\n') + } + return sb.String() +} diff --git a/onebot/utils_test.go b/onebot/utils_test.go new file mode 100644 index 0000000..8bdf8d5 --- /dev/null +++ b/onebot/utils_test.go @@ -0,0 +1,12 @@ +package main + +import ( + "fmt" + "testing" +) + +func TestFileToBase64(t *testing.T) { + // 测试文件不存在 + f, _ := FileToBase64("/Users/yincong/Desktop/2_base64.txt") + fmt.Println(f) +} diff --git a/onebot/video_builder.go b/onebot/video_builder.go index 014e908..27b4f29 100644 --- a/onebot/video_builder.go +++ b/onebot/video_builder.go @@ -1,34 +1,17 @@ package main import ( - "crypto/rand" "encoding/hex" "fmt" - mathrand "math/rand" "strconv" "time" - "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto" + wxproto "github.com/yincongcyincong/weixin-macos/onebot/proto" "google.golang.org/protobuf/proto" ) // BuildVideoMsgProto 构建发送视频消息的protobuf并返回hex编码的字符串 func BuildVideoMsgProto(sender, targetId, cdnKey, aesKey, md5Key, videoId string, duration int32, videoSize int32) (string, error) { - // 生成16字节随机client_proof - clientProof := make([]byte, 16) - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - randBytes := make([]byte, 16) - rand.Read(randBytes) - for i := range clientProof { - clientProof[i] = chars[int(randBytes[i])%len(chars)] - } - - // 生成5字节varint范围的msgId (2^28 ~ 2^35-1) - msgId := mathrand.Int63n(1<<35-1<<28) + (1 << 28) - - // header.unknown4: 同图片proto - const headerUnknown4 int64 = -228321745 - // client_msg_id: targetId_timestamp_160_xwechat_1 timestamp := time.Now().Unix() clientMsgId := targetId + "_" + strconv.FormatInt(timestamp, 10) + "_160_xwechat_1" @@ -39,11 +22,11 @@ func BuildVideoMsgProto(sender, targetId, cdnKey, aesKey, md5Key, videoId string msg := &wxproto.WxSendVideoMsg{ Header: &wxproto.VideoMsgHeader{ Flag: []byte{0x00}, - MsgId: msgId, - ClientProof: clientProof, - Unknown4: headerUnknown4, - SysInfo: []byte("UnifiedPCMac 26 arm64"), - Unknown6: 118, + SessionId: int64(globalSessionId), + ClientProof: globalClientProof, + DeviceId: int64(globalDeviceId), + Platform: []byte("UnifiedPCMac 26 arm64"), + Version: 118, }, ClientMsgId: []byte(clientMsgId), Sender: []byte(sender), @@ -76,7 +59,7 @@ func BuildVideoMsgProto(sender, targetId, cdnKey, aesKey, md5Key, videoId string Md5Key: []byte(md5Key), VideoId: []byte(videoId), // Unknown38: 0, // 需要手动追加 - Md5Key2: []byte(md5Key), + Md5Key2: []byte(md5Key), CdnKey3: []byte(cdnKey), AesKey3: []byte(aesKey), VideoSize3: videoSize, diff --git a/onebot/worker.go b/onebot/worker.go index c4792be..08b3cd8 100644 --- a/onebot/worker.go +++ b/onebot/worker.go @@ -6,6 +6,7 @@ import ( "encoding/json" "encoding/xml" "errors" + "fmt" "os" "runtime/debug" "sync/atomic" @@ -19,11 +20,9 @@ func SendWorker() { go SendWorker() } }() - + for { select { - case <-finishChan: - Info("收到完成信号") case m, ok := <-msgChan: if !ok { Fatal("发送通道关闭") @@ -35,28 +34,37 @@ func SendWorker() { } func SendWechatMsg(m *SendMsg) { + var sendErr error + defer func() { + if m.ResultChan != nil { + m.ResultChan <- sendErr + } + }() + time.Sleep(time.Duration(config.SendInterval) * time.Millisecond) currTaskId := atomic.AddInt64(&taskId, 1) Info("📩 收到任务", "task_id", currTaskId, "type", m.Type) - + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - + targetId := m.UserId if m.GroupID != "" { targetId = m.GroupID } - + if targetId == "" { Error("目标为空", "task_id", currTaskId, "target_id", targetId) + sendErr = errors.New("target is empty") return } - + switch m.Type { case "text": protoHex, err := BuildTextMsgProto(targetId, m.Content, m.AtUser) if err != nil { Error("构建文本protobuf失败", "err", err) + sendErr = err return } payloadHex := BuildSendPayload(currTaskId, "text") @@ -64,39 +72,50 @@ func SendWechatMsg(m *SendMsg) { Info("📩 发送文本任务执行结果", "result", result, "task_id", currTaskId, "target_id", targetId, "at_user", m.AtUser) if result != "1" { Error("发送文本失败", "task_id", currTaskId, "target_id", targetId, "result", result) + sendErr = errors.New("send text failed") return } case "image": targetPath, md5Str, err := SaveBase64Image(m.Content) if err != nil { Error("保存图片失败", "err", err) + sendErr = err return } - + uploadPayloadHex := BuildUploadPayload("img") result := fridaScript.ExportsCall("triggerUploadImg", targetId, md5Str, targetPath, uploadPayloadHex) Info("📩 上传图片任务执行结果", "result", result, "target_id", targetId, "md5", md5Str, "path", targetPath) if result != "0" { Error("上传图片失败", "target_id", targetId, "md5", md5Str, "result", result) + sendErr = errors.New("upload image failed") return } + if m.ResultChan != nil { + pendingResultMap.Store(targetId, m.ResultChan) + m.ResultChan = nil // 不让 defer 发送结果 + } + return case "send_image": protoHex, err := BuildImgMsgProto(myWechatId, targetId, m.CdnKey, m.AesKey, m.Md5Key) if err != nil { Error("构建图片protobuf失败", "err", err) + sendErr = err return } payloadHex := BuildSendPayload(currTaskId, "img") result := fridaScript.ExportsCall("triggerSendImgMessage", currTaskId, myWechatId, targetId, protoHex, payloadHex) Info("📩 发送图片任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId) if result != "1" { - Error("上传图片失败", "task_id", currTaskId, "target_id", targetId, "result", result) + Error("发送图片失败", "task_id", currTaskId, "target_id", targetId, "result", result) + sendErr = errors.New("send image failed") return } case "video": targetPath, md5Str, err := SaveBase64Image(m.Content) if err != nil { Error("保存图片失败", "err", err) + sendErr = err return } @@ -118,8 +137,14 @@ func SendWechatMsg(m *SendMsg) { Info("📩 上传视频任务执行结果", "result", result, "target_id", targetId, "md5", md5Str, "path", targetPath, "duration", info.Duration, "size", info.VideoSize) if result != "0" { Error("上传视频失败", "target_id", targetId, "md5", md5Str, "result", result) + sendErr = errors.New("upload video failed") return } + if m.ResultChan != nil { + pendingResultMap.Store(targetId, m.ResultChan) + m.ResultChan = nil + } + return case "send_video": var duration, videoSize int32 if info, ok := videoInfoMap.LoadAndDelete(targetId); ok { @@ -130,6 +155,7 @@ func SendWechatMsg(m *SendMsg) { protoHex, err := BuildVideoMsgProto(myWechatId, targetId, m.CdnKey, m.AesKey, m.Md5Key, m.VideoId, duration, videoSize) if err != nil { Error("构建视频protobuf失败", "err", err) + sendErr = err return } payloadHex := BuildSendPayload(currTaskId, "video") @@ -137,6 +163,7 @@ func SendWechatMsg(m *SendMsg) { Info("📩 发送视频任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId, "duration", duration, "size", videoSize) if result != "1" { Error("发送视频失败", "task_id", currTaskId, "target_id", targetId, "result", result) + sendErr = errors.New("send video failed") return } case "download": @@ -156,6 +183,7 @@ func SendWechatMsg(m *SendMsg) { protoHex, err := BuildReplyMsgProto(myWechatId, targetId, replyInfo) if err != nil { Error("构建回复protobuf失败", "err", err) + sendErr = err return } payloadHex := BuildSendPayload(currTaskId, "reply") @@ -163,15 +191,22 @@ func SendWechatMsg(m *SendMsg) { Info("📩 发送回复任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId) if result != "1" { Error("发送回复失败", "task_id", currTaskId, "target_id", targetId, "result", result) + sendErr = errors.New("send reply failed") return } } - + select { case <-ctx.Done(): Error("任务执行超时!", "taskId", currTaskId) - case <-finishChan: - Info("收到完成信号,任务完成", "taskId", currTaskId) + sendErr = errors.New("send timeout") + case resp := <-buf2RespChan: + if resp.Err != nil { + Error("收到buf2resp失败信号", "taskId", currTaskId, "msg_type", resp.MsgType, "err", resp.Err) + sendErr = resp.Err + return + } + Info("收到buf2resp完成信号,任务完成", "taskId", currTaskId, "msg_type", resp.MsgType, "data_len", len(resp.Data)) } } @@ -182,11 +217,14 @@ func HandleMsg(jsonData []byte) ([]byte, error) { Error("解析消息失败", "err", err) return nil, err } - myWechatId = m.SelfID + + if myWechatId == "" && m.SelfID != "" { + myWechatId = m.SelfID + } if m.GroupId != "" { userID2NicknameMap.Store(m.GroupId+"_"+m.UserID, m.Sender.Nickname) } - + for _, msg := range m.Message { switch msg.Type { case "record": @@ -204,15 +242,15 @@ func HandleMsg(jsonData []byte) ([]byte, error) { Error("XML解析失败", "err", err) return nil, err } - + path, err := GetDownloadPath(fileMsg.Image.MidImgURL, fileMsg.Image.AesKey) if err != nil { Error("获取文件路径失败", "err", err) return nil, err } - + msg.Data.URL = "file://" + path - + case "file": var fileMsg FileMsg err = xml.Unmarshal([]byte(msg.Data.Text), &fileMsg) @@ -225,7 +263,7 @@ func HandleMsg(jsonData []byte) ([]byte, error) { Error("获取文件路径失败", "err", err) return nil, err } - + msg.Data.URL = "file://" + path case "video": var fileMsg FileMsg @@ -239,7 +277,7 @@ func HandleMsg(jsonData []byte) ([]byte, error) { Error("获取文件路径失败", "err", err) return nil, err } - + msg.Data.URL = "file://" + path case "face": var fileMsg FileMsg @@ -249,8 +287,11 @@ func HandleMsg(jsonData []byte) ([]byte, error) { return nil, err } - // 优先thumburl,为空则用externurl - emojiUrl := fileMsg.Emoji.ThumbUrl + // 优先cdnurl,为空则用thumburl,再为空则用externurl + emojiUrl := fileMsg.Emoji.CdnUrl + if emojiUrl == "" { + emojiUrl = fileMsg.Emoji.ThumbUrl + } if emojiUrl == "" { emojiUrl = fileMsg.Emoji.ExternUrl } @@ -260,13 +301,13 @@ func HandleMsg(jsonData []byte) ([]byte, error) { Error("下载表情失败", "err", err) return nil, err } - + path, err := DetectAndSaveImage(data) if err != nil { Error("保存表情失败", "err", err) return nil, err } - + msg.Data.URL = "file://" + path } } @@ -280,17 +321,17 @@ func GetDownloadPath(cdnUrl, aesKeyStr string) (string, error) { if downloadReq.FilePath != "" { return downloadReq.FilePath, nil } - + // 检查数据是否还在接收中 timeSinceLastAppend := time.Now().UnixMilli() - downloadReq.LastAppendTime Info("文件等待下载", "url", cdnUrl, "times", i, "last_append_time", timeSinceLastAppend) - + // 如果数据仍在接收中(1秒内有新数据),继续等待 if timeSinceLastAppend < 1000 && i < 9 { time.Sleep(2 * time.Second) continue } - + // 数据接收完成,尝试解密 if len(downloadReq.Media) > 0 { aesKey, err := hex.DecodeString(aesKeyStr) @@ -304,15 +345,52 @@ func GetDownloadPath(cdnUrl, aesKeyStr string) (string, error) { userID2FileMsgMap.Delete(cdnUrl) return "", err } - + downloadReq.FilePath = filePath downloadReq.Media = nil return filePath, nil } } - + time.Sleep(2 * time.Second) } - + return "", errors.New("文件下载超时或数据为空") } + +// HandleBuf2Resp 处理所有消息类型的buf2resp响应 +func HandleBuf2Resp(msgType string, data []byte) { + Info("收到buf2resp响应", "msg_type", msgType, "data_len", len(data)) + + if len(data) == 0 { + Error("buf2resp响应数据为空", "msg_type", msgType) + buf2RespChan <- &Buf2RespData{ + MsgType: msgType, + Data: data, + Err: errors.New("response data is empty"), + } + return + } + + ret, errMsg, err := ParseSendMsgResponse(data) + if err != nil { + Info("buf2resp响应无法提取错误码,视为成功", "msg_type", msgType, "err", err) + } + + // 判断错误码是否为0 + if ret != 0 { + Error("buf2resp响应错误", "msg_type", msgType, "ret", ret, "errMsg", errMsg) + buf2RespChan <- &Buf2RespData{ + MsgType: msgType, + Data: data, + Err: fmt.Errorf("response error, ret=%d, errMsg=%s", ret, errMsg), + } + return + } + + Info("buf2resp响应成功", "msg_type", msgType) + buf2RespChan <- &Buf2RespData{ + MsgType: msgType, + Data: data, + } +} diff --git a/wechat_version/4_1_9_58_mac.json b/wechat_version/4_1_9_58_mac.json index c75b38d..d2420e4 100644 --- a/wechat_version/4_1_9_58_mac.json +++ b/wechat_version/4_1_9_58_mac.json @@ -3,6 +3,8 @@ "req2bufEnterAddr": "0x39f18e0", "req2bufExitAddr": "0x39f29f4", "buf2RespAddr": "0x3a17308", + "blrX8Addr": "0x39F1960", + "autoBufferWriteFunc": "0x3A18970", "uploadImageAddr": "0x4d6c8f8", "cndOnCompleteAddr": "0x39adf14", @@ -14,17 +16,5 @@ "startDownloadMedia": "0x4d6d560", "downloadImagAddr": "0x4e90054", "downloadFileAddr": "0x4e2ef8c", - "downloadVideoAddr": "0x4e48d6c", - - "textCallbackFuncAddr": "0x2694200", - "sendMessageCallbackFunc": "0x8c714e0", - "imageCallbackFuncAddr": "0x268ABC4", - "imgMessageCallbackFunc": "0x8c70fa8", - "videoCallbackFuncAddr": "0x26FA170", - "videoMessageCallbackFunc": "0x8c73d58", - "replyCallbackFuncAddr": "0x2628C10", - "replyMessageCallbackFunc": "0x8c6e668", - "voiceCallbackFuncAddr": "0x31CB034", - "voiceMessageCallbackFunc": "0x8cb3730" + "downloadVideoAddr": "0x4e48d6c" } -