156 Commits

Author SHA1 Message Date
48b720bdd7 Merge remote-tracking branch 'origin/master' 2024-01-20 03:03:07 +08:00
2038d81ce8 Shamrock: 调整包拦截策略 2024-01-20 03:02:55 +08:00
81be383b5f revert 2024-01-18 20:05:18 +08:00
0fb88e3e44 give more love to 2024-01-18 16:51:44 +08:00
e92b04ad0f Shamrock: fix build error #201 2024-01-18 09:58:16 +08:00
160d1a11ac Shamrock: #201 2024-01-18 09:49:56 +08:00
b9cfe73eae Shamrock: fix #150 2024-01-17 04:53:07 +08:00
8d6d984849 Shamrock: 尝试修复重复Client连接 #187 2024-01-17 04:15:24 +08:00
25fe9fab37 Shamrock: fix 9.0.8 2024-01-17 03:52:49 +08:00
ba7058a838 add retryCnt 2024-01-14 16:24:37 +08:00
0858395e60 add retryCnt 2024-01-14 15:49:44 +08:00
fdd769d9ff should fix #176 2024-01-10 00:17:53 +08:00
f47ae69653 Merge pull request #193 from MrXiaoM/fix-group-ban
支持全员禁言通知事件
2024-01-07 15:13:43 +08:00
f311ae3797 Merge pull request #194 from MrXiaoM/fix-group-title-change
修复群头衔更改通知报错
2024-01-07 15:13:19 +08:00
5a941f889f add group-title-change notify busiId 2024-01-07 04:39:42 +08:00
69e50c6f93 fix group title change notice on 8.9.80 2024-01-07 04:38:45 +08:00
f7ac3a5d23 whole group ban notice support (go-cqhttp) 2024-01-07 03:52:29 +08:00
b6b54a805e more debug log when error 2024-01-07 03:31:49 +08:00
131f56a468 Shamrock: fix #191 2024-01-06 18:53:42 +08:00
e45e9e7fa0 Merge pull request #189 from super1207/patch-3
fix groupid get
2024-01-05 10:46:32 +08:00
cac0aad1f2 fix groupid get 2024-01-04 19:40:01 +08:00
2c1bd9e726 Shamrock: fix #186 2024-01-04 00:30:36 +08:00
2645e8f451 Merge remote-tracking branch 'origin/master' 2024-01-03 17:45:43 +08:00
e92c227bba Shamrock: 对QQ9支持,并尝试修复#181 2024-01-03 17:45:32 +08:00
a31fe92c0b feat: 获取频道列表接口 (TODO 状态 返回空数组) 2024-01-01 19:01:38 +08:00
70cb876439 Merge remote-tracking branch 'origin/master' 2024-01-01 19:00:48 +08:00
9aa4c37354 fix: WebSocket 部分操作未正常返回数据 (close #182) 2024-01-01 19:00:28 +08:00
d44150ea1a Shamrock: 在获取某些字段错误时,将字段值定义为0 #150 2024-01-01 00:42:47 +08:00
0360c81bee Shamrock: fix #173 2024-01-01 00:40:55 +08:00
cb904c1f1c Shamrock: fix #179 2024-01-01 00:38:44 +08:00
bc754db959 Merge remote-tracking branch 'origin/master' 2024-01-01 00:37:35 +08:00
8df799a6e4 Shamrock: fix #180 2024-01-01 00:37:12 +08:00
71dd9469ca Merge pull request #178 from super1207/patch-2
fix cq code parse
2023-12-27 20:00:49 +09:00
79788d2cdc fix cq code parse 2023-12-26 19:53:00 +08:00
c28c9981c4 Shamrock: 修复QQ收藏异常 2023-12-23 05:13:16 +08:00
8fadd0016a Shamrock: fix #171 #165 2023-12-23 05:06:37 +08:00
77504d68fd Shamrock: 支持获取QQ收藏的列表 /fav/get_item_list 2023-12-23 04:47:11 +08:00
1490450178 Shamrock: 支持获取指定QQ收藏的内容 /fav/get_item_content 2023-12-23 04:16:11 +08:00
88beaf8b6f Shamrock: 支持添加QQ收藏(图片) /fav/add_image_msg 2023-12-22 14:19:56 +08:00
df25b0bc76 Shamrock: 支持添加QQ收藏(文本) /fav/add_rich_media_msg 2023-12-22 03:08:46 +08:00
d07eea7766 Shamrock: Shamrock更新后自动重新启动 2023-12-22 02:26:39 +08:00
5000453002 Shamrock: 完成QQ收藏数据分析 2023-12-21 02:25:20 +08:00
35c82fcc51 Shamrock: fix #169 2023-12-21 00:55:56 +08:00
89a4912ed7 Shamrock: fix #166 2023-12-20 18:58:17 +08:00
aeabc66067 Shamrock: 兼容性正反向HTTP调整 2023-12-20 18:52:24 +08:00
ccbfc9a1e1 Shamrock: fix #159 2023-12-19 22:11:22 +08:00
31936feb98 Merge remote-tracking branch 'origin/master' 2023-12-19 22:10:03 +08:00
538db69754 Shamrock: fix #159 2023-12-19 22:09:23 +08:00
25ea5bd6a8 Merge pull request #162 from callng/master
`Shamrock`: Upgrade build tools
2023-12-19 22:07:32 +08:00
460cd84258 Shamrock: Upgrade build tools 2023-12-19 21:13:28 +08:00
3f9613c43c Shamrock: 输出心跳日志 2023-12-19 21:07:28 +08:00
34eccda233 Shamrock: Määritetty tunnus ei vaadi suuraakkosia 2023-12-19 01:21:08 +08:00
741d2c7a84 Shamrock: fix #117 2023-12-19 01:13:39 +08:00
6ee5ceb321 Shamrock: fix #155 2023-12-18 21:15:56 +08:00
6107ec6ffb Shamrock: fix #156 2023-12-17 11:40:05 +08:00
4ef014a8ac Shamrock: fix card event and kickme operation 2023-12-13 15:09:46 +08:00
135a7c2f56 Shamrock: 优化构建流程 2023-12-13 01:23:40 +08:00
420f11784d Shamrock: fix field error 2023-12-13 00:22:06 +08:00
951e7462c4 Shamrock: add shut_up_timestamp field to get_group_member_list 2023-12-13 00:18:57 +08:00
1b0550b5e1 readme: fix broken link 2023-12-12 17:17:31 +08:00
919c4a7d80 Shamrock: fix #149 again 2023-12-12 11:11:21 +08:00
dcb2b0a26f Shamrock: fix #149 2023-12-12 10:28:24 +08:00
d388e5df0c Shamrock: fix #149 2023-12-11 14:29:07 +08:00
5776524579 Shamrock: 好友请求系统消息 2023-12-10 18:40:33 +08:00
1d0a0731fb Shamrock: 修复群消息撤回事件群号获取错误 2023-12-10 01:57:38 +08:00
4dafa75944 Shamrock: 修复群禁言事件群号获取错误 #148 2023-12-10 01:21:38 +08:00
85cdc86b07 Merge pull request #144 from PisLuanyao/master
`Clover.cpp`: NoxAppPlayer里的鸡脚so
2023-12-10 01:09:21 +08:00
cd19426d1b Clover.cpp: NoxAppPlayer
稍微动了一下,方便看,希望没搞错
2023-12-09 19:14:22 +08:00
48b1b40e1c Clover.cpp: NoxAppPlayer
也许是无效的?
2023-12-09 18:56:00 +08:00
50d469cc45 Clover.cpp: NoxAppPlayer
夜神模拟器里的鸡脚so
2023-12-09 18:41:53 +08:00
37f74d5284 Shamrock: 优化检测逻辑 2023-12-09 16:18:30 +08:00
adb7b12c16 Shamrock: feat 群打卡 2023-12-08 21:52:35 +08:00
27791cc848 Shamrock: fix friend request type typo 2023-12-08 12:54:34 +08:00
bd45523e25 Shamrock: 修正被动WebSocket日志 2023-12-07 23:44:55 +08:00
c0f2242679 Shamrock: 修复防止系统打盹错误 2023-12-07 00:24:06 +08:00
c309a2b3ed Shamrock: fix 部分事件日志错误打印 2023-12-06 16:42:00 +08:00
48c9048a00 Merge branch 'master' of github.com:whitechi73/OpenShamrock 2023-12-06 15:53:11 +08:00
4d2f7a794b Shamrock: 补充群成员信息area、age和level字段 2023-12-06 15:53:01 +08:00
ab6e431872 Merge remote-tracking branch 'origin/master' 2023-12-06 09:44:20 +08:00
9423df2670 Shamrock: 修复回复类型id错误 2023-12-06 09:44:10 +08:00
a2b3e42eee Shamrock: fix #125 2023-12-06 00:52:57 +08:00
d9a045bbf0 Shamrock: 修复构建 2023-12-05 18:26:15 +08:00
42ca17339e Shamrock: 不处理发给自己的消息 2023-12-05 18:24:22 +08:00
003c4d4456 Shamrock: 修复群戳一戳丶群打卡获取的群号错误 2023-12-04 19:42:46 +08:00
ec39aa7bc3 Shamrock: fix #124 2023-12-04 15:07:33 +08:00
4e3870a512 Shamrock: fix #124 2023-12-04 12:58:53 +08:00
97534b01a6 Merge pull request #121 from Miuzarte/patch-1
go-cqhttp like card and nickname field
2023-12-03 12:43:16 +08:00
61bed61bfb go-cqhttp like card and nickname field 2023-12-03 02:15:17 +08:00
a0ff4782db Merge remote-tracking branch 'origin/master' 2023-12-02 23:55:57 +08:00
41dd1de8f8 Shamrock: try fix #120 2023-12-02 23:55:48 +08:00
fa6634d6af Shamrock: fix #111 and add group sign event 2023-12-02 22:59:37 +08:00
6201d12f5f Shamrock: error fix 2023-12-02 18:08:35 +08:00
b2adc5cedf Shamrock: fix #106 2023-12-02 17:54:53 +08:00
0bb871bf01 Shamrock: fix #115 2023-12-02 17:36:34 +08:00
dc969440ee Shamrock: fix #116 2023-12-02 16:21:16 +08:00
b9b6e133d0 Shamrock: fix #117 2023-12-02 16:19:05 +08:00
b5a9884448 Shamrock: fix 群头衔推送 2023-12-02 11:11:22 +08:00
bffb7caf04 Shamrock: A60's PING-PONG 2023-12-02 09:51:39 +08:00
2c3466b4c3 Shamrock: fix 96 crash 2023-12-01 19:30:48 +08:00
007e5fef2f Merge branch 'master' of github.com:whitechi73/OpenShamrock 2023-12-01 15:22:04 +08:00
48773cc47c Shamrock: JSON メッセージのクラッシュと QQ ミュージックのカバーを修正 2023-12-01 15:21:58 +08:00
b2ad4438ab Shamrock: fix #114 2023-12-01 14:47:33 +08:00
2fdcfe332b Shamrock: fix #109 2023-11-30 22:38:44 +08:00
7d8772ebf6 Shamrock: スーパーアンチチェックのオフを許可 2023-11-30 21:36:57 +08:00
2a75160ef8 Merge remote-tracking branch 'origin/master' 2023-11-30 21:33:43 +08:00
76bd58d984 Shamrock: スーパーアンチチェックのオフを許可 2023-11-30 21:33:32 +08:00
39120bdeae Shamrock: fix 群精华消息推送 2023-11-30 19:04:25 +08:00
7b07698f7b Shamrock: fix #110 2023-11-30 12:27:07 +08:00
5c10a5a04e Shamrock: 支持篮球超表情, 新猜拳超表情 2023-11-30 10:28:12 +08:00
3a0dc41329 Shamrock: 支持NTQQ骰子消息(new_dice) 2023-11-29 22:11:36 +08:00
64c800c945 Merge remote-tracking branch 'origin/master' 2023-11-29 10:11:35 +08:00
ecb3cea5a5 Shamrock: fix #105 2023-11-29 10:11:24 +08:00
8e0ae6f85b Shamrock: fix #104 2023-11-28 23:49:21 +08:00
9d893b481d Shamrock: fix group ban user_id error 2023-11-28 17:33:33 +08:00
85aaa54e4e Shamrock: friend poke event detail fix #103 2023-11-28 17:19:08 +08:00
c6dad5677c Shamrock: #103 2023-11-28 17:10:19 +08:00
80a4a208b9 Shamrock: 尝试修复 #98 2023-11-27 12:06:02 +08:00
ae663e6b2e Shamrock: 中二通事 2023-11-27 01:13:40 +08:00
780f3577a5 Shamrock: optimize clover.cpp 2023-11-27 00:42:15 +08:00
3518f974cc Shamrock: bypass emu detection 2023-11-27 00:28:43 +08:00
911b003f7f Shamrock: Modify required fields according to the document 2023-11-27 00:23:02 +08:00
69bc80e9b3 Merge branch 'master' of github.com:whitechi73/OpenShamrock 2023-11-27 00:18:56 +08:00
da0b74db1a Shamrock: fix #92 2023-11-27 00:18:51 +08:00
7212938df3 Shamrock: fix troopowneruin 2023-11-26 22:34:00 +08:00
ae1e78b267 Merge remote-tracking branch 'origin/master' 2023-11-26 22:29:18 +08:00
b7266e490f Shamrock: アンチシミュレータ検出 2023-11-26 22:29:11 +08:00
6b4a429821 Shamrock: fix forward msg nesting(x 2023-11-26 22:06:26 +08:00
4a4507dfcd Shamrock: add switch of anti trace 2023-11-26 19:28:36 +08:00
f63bcabf1b Shamrock: 添加native检测绕过模板 2023-11-26 18:32:55 +08:00
4932b36ee1 Shamrock: fix: グループリクエストイベントの通知と処理ロジックを最適化する 2023-11-26 12:59:48 +08:00
8c307c4f6e Shamrock: fix #90 2023-11-25 20:28:14 +08:00
8d8846fafb Shamrock: 复皆如何推送,遗类也。 2023-11-25 20:05:32 +08:00
544e216ddb Merge remote-tracking branch 'origin/master' 2023-11-25 19:55:09 +08:00
4fedab719b Shamrock: Support télécharger un fichier obtenir un ID de fichier 2023-11-25 19:55:00 +08:00
75a567d5cd Shamrock: fix 重複したグループ参加イベントを削除する 2023-11-25 19:47:35 +08:00
1a814e565a Shamrock: fix #89 2023-11-25 18:50:48 +08:00
5ea260c24b Shamrock: #89 2023-11-25 18:31:08 +08:00
2d57dc021d Merge branch 'master' of github.com:whitechi73/OpenShamrock 2023-11-25 13:51:21 +08:00
dabe2ea886 Shamrock: グループファイルのハッシュ値が増加する 2023-11-25 13:51:16 +08:00
673902e514 Shamrock: fix typo 2023-11-25 13:33:06 +08:00
5062ff7c3a Shamrock: ログスライス #78 2023-11-25 11:53:39 +08:00
0de6f851a6 Merge remote-tracking branch 'origin/master' 2023-11-25 11:29:26 +08:00
c758b1576d Shamrock: アクティブWebSocketハートビートの修復 2023-11-25 11:29:06 +08:00
5ba8bd11e2 Shamrock: essence, card change and title change event 2023-11-25 02:09:16 +08:00
679b7619ce Shamrock: wsハートビート間隔の制御をサポート 2023-11-25 00:42:10 +08:00
282233131a Shamrock: fix 非常に奇妙なフォームデータの問題 2023-11-24 15:18:50 +08:00
edf857bcb6 Shamrock: _send_group_notice 2023-11-24 13:21:22 +08:00
cd1d1e928a Merge branch 'master' of github.com:whitechi73/OpenShamrock 2023-11-24 01:00:59 +08:00
45d6421153 Shamrock: _get_group_notice 2023-11-24 01:00:53 +08:00
8c6f529b4b Merge pull request #76 from DouchChunFeng/master
有个空格
2023-11-24 00:24:57 +08:00
b23620b5ef Shamrock: get_essence_msg_list 2023-11-24 00:21:05 +08:00
e09e00fcd3 有个空格 2023-11-24 00:06:50 +08:00
0d35d5834b Shamrock: 金メダル免除、システムレベルの保活をサポートする 2023-11-23 23:53:11 +08:00
ee8dc75be3 Shamrock: fix: implement request type event 2023-11-23 19:33:50 +08:00
5f91be547e Shamrock: fix group apply flag error 2023-11-23 18:15:20 +08:00
7439622cd6 Shamrock: fix #73 2023-11-23 12:51:26 +08:00
148 changed files with 5916 additions and 1321 deletions

View File

@ -6,7 +6,7 @@ labels: bug
---
警告: 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭
- 确保您使用的是 [最新开发版本](https://github.com/whitechi73/Shamrock/actions/workflows/build-apk.yml) 的 Shamrock.
- 确保您使用的是 [最新开发版本](https://github.com/whitechi73/OpenShamrock/actions/workflows/build-apk.yml) 的 Shamrock.
- 确保您的问题尚未在 Issues 列表中提出.
- 确保您的问题不是由于您的代码错误导致的.
@ -22,5 +22,6 @@ labels: bug
- Shamrock 版本:
- Android 版本:
- LSPosed 框架版本:
- 设备的制造商和型号:
- 设备的 CPU 架构:

View File

@ -7,7 +7,7 @@ labels: enhancement
警告: 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭
- 确保您使用的是 [最新开发版本](https://github.com/whitechi73/Shamrock/actions/workflows/build-apk.yml) 的 Shamrock.
- 确保您使用的是 [最新开发版本](https://github.com/whitechi73/OpenShamrock/actions/workflows/build-apk.yml) 的 Shamrock.
- 确保您的功能请求尚未在 Issues 列表中提出.
- 确保您的功能请求是与 Shamrock 相关的,且可以实现.

View File

@ -13,24 +13,38 @@ on:
jobs:
build:
name: Build APK
name: Build Shamrock
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
distribution: "temurin"
fetch-depth: 0
- name: Setup JDK 17
uses: actions/setup-java@v4.0.0
with:
java-version: 17
cache: 'gradle'
distribution: "adopt"
- name: Setup cmake
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "cmake;3.22.1" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
echo "sdk.dir=${ANDROID_HOME}" > local.properties
- name: Cache Gradle Dependencies
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
!~/.gradle/caches/build-cache-*
key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
restore-keys: gradle-deps
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.9.0
- name: Cache Gradle Build
uses: actions/cache@v3
with:
path: |
~/.gradle/caches/build-cache-*
~/.gradle/buildOutputCleanup/cache.properties
key: gradle-builds-core-${{ github.sha }}
restore-keys: gradle-builds
- name: Build with Gradle
run: |
@ -46,42 +60,41 @@ jobs:
KEY_ALIAS: ${{ secrets.SIGN_ALIAS }}
KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
- name: Install aapt
run: sudo apt-get update && sudo apt-get install -y aapt
- name: Set Shamrock Version
run: |
apk_file=${{ env.APK_FILE_ALL }}
apk_dump=$(aapt dump badging "$apk_file")
version_name=$(sed -n "s/.*versionName='\([^']*\)'.*/\1/p" <<< "$apk_dump")
echo "SHAMROCK_VERSION=$version_name" >> $GITHUB_ENV
version_name_all=$(basename -s .apk "${{ env.APK_FILE_ALL }}")
version_name_arm64=$(basename -s .apk "${{ env.APK_FILE_ARM64 }}")
version_name_x86_64=$(basename -s .apk "${{ env.APK_FILE_X86_64 }}")
echo "SHAMROCK_VERSION_ALL=$version_name_all" >> $GITHUB_ENV
echo "SHAMROCK_VERSION_ARM64=$version_name_arm64" >> $GITHUB_ENV
echo "SHAMROCK_VERSION_x86_64=$version_name_x86_64" >> $GITHUB_ENV
- name: Show Artifacts SHA256
run: |
echo "### Build Success :rocket:" >> $GITHUB_STEP_SUMMARY
echo "|ABI|SHA256|" >> $GITHUB_STEP_SUMMARY
echo "|:--------:|:----------|" >> $GITHUB_STEP_SUMMARY
all=($(sha256sum ${{ env.APK_FILE_ALL }}))
all=($(sha256sum "${{ env.APK_FILE_ALL }}"))
echo "|all|$all" >> $GITHUB_STEP_SUMMARY
arm64=($(sha256sum ${{ env.APK_FILE_ARM64 }}))
arm64=($(sha256sum "${{ env.APK_FILE_ARM64 }}"))
echo "|arm64|$arm64" >> $GITHUB_STEP_SUMMARY
x86_64=($(sha256sum ${{ env.APK_FILE_X86_64 }}))
x86_64=($(sha256sum "${{ env.APK_FILE_X86_64 }}"))
echo "|x86_64|$x86_64" >> $GITHUB_STEP_SUMMARY
- name: Upload ALL APK RELEASE
uses: actions/upload-artifact@v3
with:
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-all
path: ${{ env.APK_FILE_ALL }}
name: "${{ env.SHAMROCK_VERSION_ALL }}"
path: "${{ env.APK_FILE_ALL }}"
- name: Upload ARM64 APK RELEASE
uses: actions/upload-artifact@v3
with:
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-arm64
path: ${{ env.APK_FILE_ARM64 }}
name: "${{ env.SHAMROCK_VERSION_ARM64 }}"
path: "${{ env.APK_FILE_ARM64 }}"
- name: Upload X86_64 APK RELEASE
uses: actions/upload-artifact@v3
with:
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-x86_64
path: ${{ env.APK_FILE_X86_64 }}
name: "${{ env.SHAMROCK_VERSION_x86_64 }}"
path: "${{ env.APK_FILE_X86_64 }}"

View File

@ -1,57 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: '24 7 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
timeout-minutes: 360
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'cpp', 'java' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3.6.0
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Setup JDK 17
uses: actions/setup-java@v3.12.0
with:
distribution: "temurin"
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.8.0
- name: Setup cmake
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "cmake;3.22.1" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
echo "sdk.dir=${ANDROID_HOME}" > local.properties
- name: Build
run: |
chmod +x ./gradlew
./gradlew :app:assembleAppRelease
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@ -1,73 +0,0 @@
name: Shamrock Release
on:
push:
tags:
- 'v*'
jobs:
build:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: 17
cache: 'gradle'
- name: Setup cmake
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "cmake;3.22.1" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
echo "sdk.dir=${ANDROID_HOME}" > local.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.9.0
- name: Build with Gradle
run: |
echo ${{ secrets.SIGN_KEYSTORE_BASE64 }} | base64 -d > keystore.jks
chmod +x ./gradlew
./gradlew :app:assembleRelease --build-cache --parallel --daemon --warning-mode all --stacktrace
echo "APK_FILE_ALL=$(find app/build/outputs/apk/app/release -name '*.apk')" >> $GITHUB_ENV
echo "APK_FILE_ARM64=$(find app/build/outputs/apk/arm64/release -name '*.apk')" >> $GITHUB_ENV
echo "APK_FILE_X86_64=$(find app/build/outputs/apk/x64/release -name '*.apk')" >> $GITHUB_ENV
env:
KEYSTORE_PATH: "../keystore.jks"
KEYSTORE_PASSWORD: ${{ secrets.SIGN_KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.SIGN_ALIAS }}
KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
- name: Install aapt
run: sudo apt-get update && sudo apt-get install -y aapt
- name: Set Shamrock Version
run: |
apk_file=${{ env.APK_FILE_ALL }}
apk_dump=$(aapt dump badging "$apk_file")
version_name=$(sed -n "s/.*versionName='\([^']*\)'.*/\1/p" <<< "$apk_dump")
echo "SHAMROCK_VERSION=$version_name" >> $GITHUB_ENV
- name: Show Artifacts SHA256
run: |
echo "### Build Success :rocket:" >> $GITHUB_STEP_SUMMARY
echo "|ABI|SHA256|" >> $GITHUB_STEP_SUMMARY
echo "|:--------:|:----------|" >> $GITHUB_STEP_SUMMARY
all=($(sha256sum ${{ env.APK_FILE_ALL }}))
echo "|all|$all" >> $GITHUB_STEP_SUMMARY
arm64=($(sha256sum ${{ env.APK_FILE_ARM64 }}))
echo "|arm64|$arm64" >> $GITHUB_STEP_SUMMARY
x86_64=($(sha256sum ${{ env.APK_FILE_X86_64 }}))
echo "|x86_64|$x86_64" >> $GITHUB_STEP_SUMMARY
- name: Release
uses: marvinpinto/action-automatic-releases@latest
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
automatic_release_tag: Shamrock v${{ env.SHAMROCK_VERSION }}
files: |
${{ env.APK_FILE_ALL }}
${{ env.APK_FILE_ARM64 }}
${{ env.APK_FILE_X86_64 }}

View File

@ -10,16 +10,17 @@
![][onebot-12]
[![][license]](LICENSE)
[下载][download-link] | [部署][deploy-link] | [接口][api-link] | [文档][docs-link] | [加群][group-link]
[下载][download-link] | [部署][deploy-link] | [接口][api-link] | [文档][docs-link]
</div>
## 简介
☘ 基于 Xposed 实现 OneBot 标准的 QQ 机器人框架,原作者[**fuqiuluo**](https://github.com/fuqiuluo)已脱离开发接下来由白池接手哦本项目为OpenShamrock不会有任何收费行为欢迎大家的加入
☘ 基于 Lsposed(**Non**-Riru) 实现 OneBot 标准的 QQ 机器人框架,原作者[**fuqiuluo**](https://github.com/fuqiuluo)已脱离开发接下来由白池接手哦本项目为OpenShamrock不会有任何收费行为欢迎大家的加入
> 本项目仅提供学习与交流用途请在24小时内删除。
> 本项目目的是研究 Xposed 和 LSPosed 框架的使用。 Epic 框架开发相关知识。
> Riru可能导致封禁请减少使用。
> 如有违反法律,请联系删除。
> 请勿在任何平台宣传,宣扬,转发本项目,请勿恶意修改企业安装包造成相关企业产生损失,如有违背,必将追责到底。
> 官方论坛,[点我直达](https://forum.libfekit.so/)
@ -29,8 +30,6 @@
- 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。
- 平行部署:可多平台部署,未来将会支持 Docker 部署的教程。
> 若您追求小而轻便的Bot服务, [Chronocat](https://chronocat.vercel.app/)是您的不二之选。
## 权限声明
> 如出现未在此处声明的权限,请警惕 Shamrock 是否被修改/植入恶意代码
@ -45,7 +44,7 @@
## 贡献说明
<img src="https://github.com/whitechi73/OpenShamrock/assets/98259561/f04d60bc-ec40-41fc-bc15-62c146f1a1f1" width="160px"> **我可爱吗?欢迎你的到来,这里是一个很大的地方,有着无限可能,主要是有你啦!**
<img src="https://github.com/whitechi73/OpenShamrock/assets/98259561/f04d60bc-ec40-41fc-bc15-62c146f1a1f1" width="160px" alt="Shamrock"> **我可爱吗?欢迎你的到来,这里是一个很大的地方,有着无限可能,主要是有你啦!**
## 开源协议
@ -96,9 +95,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
[docs-link]: https://whitechi73.github.io/OpenShamrock/
[group-link]: https://whitechi73.github.io/OpenShamrock/group.html
[hook-system]: https://github.com/whitechi73/OpenShamrock/wiki/perm_hook_android
[hook-system]: https://github.com/whitechi73/OpenShamrock/blob/master/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/KeepAlive.kt
[voice-support]: https://whitechi73.github.io/OpenShamrock/advanced/voice.html

View File

@ -1,12 +1,5 @@
import com.android.build.api.dsl.ApplicationExtension
fun gitCommitHash(): String {
val builder = ProcessBuilder("git", "rev-parse", "--short", "HEAD")
val process = builder.start()
val reader = process.inputReader()
val hash = reader.readText().trim()
return if (hash.isNotEmpty()) ".$hash" else ""
}
import java.io.ByteArrayOutputStream
plugins {
id("com.android.application")
@ -17,19 +10,20 @@ plugins {
android {
namespace = "moe.fuqiuluo.shamrock"
ndkVersion = "25.1.8937393"
compileSdk = 33
compileSdk = 34
defaultConfig {
applicationId = "moe.fuqiuluo.shamrock"
minSdk = 24
targetSdk = 33
versionCode = (System.currentTimeMillis() / 1000).toInt()
versionName = "1.0.6-dev" + gitCommitHash()
minSdk = 27
targetSdk = 34
versionCode = getVersionCode()
versionName = "1.0.7" + ".r${getGitCommitCount()}." + getVersionName()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
@Suppress("UnstableApiUsage")
externalNativeBuild {
cmake {
cppFlags += ""
@ -50,8 +44,7 @@ android {
android.applicationVariants.all {
outputs.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
.forEach {
val abi = it.outputFileName.split("-")[1].split(".apk")[0]
val abiName = when (abi) {
val abiName = when (val abi = it.outputFileName.split("-")[1].split(".apk")[0]) {
"app" -> "all"
"x64" -> "x86_64"
else -> abi
@ -69,6 +62,7 @@ android {
println("Full architecture and full compilation.")
abiFilters.add("arm64-v8a")
abiFilters.add("x86_64")
abiFilters.add("x86")
}
}
create("arm64") {
@ -133,6 +127,11 @@ android {
}
configureAppSigningConfigsForRelease(project)
packagingOptions {
jniLibs {
useLegacyPackaging = true
}
}
}
fun configureAppSigningConfigsForRelease(project: Project) {
@ -154,10 +153,39 @@ fun configureAppSigningConfigsForRelease(project: Project) {
release {
signingConfig = signingConfigs.findByName("release")
}
debug {
signingConfig = signingConfigs.findByName("release")
}
}
}
}
fun getGitCommitCount(): Int {
val out = ByteArrayOutputStream()
exec {
commandLine("git", "rev-list", "--count", "HEAD")
standardOutput = out
}
return out.toString().trim().toInt()
}
fun getGitCommitHash(): String {
val out = ByteArrayOutputStream()
exec {
commandLine("git", "rev-parse", "--short", "HEAD")
standardOutput = out
}
return out.toString().trim()
}
fun getVersionCode(): Int {
return (System.currentTimeMillis() / 1000L).toInt()
}
fun getVersionName(): String {
return getGitCommitHash()
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")

View File

@ -3,10 +3,6 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".app.MyApplication"
@ -18,6 +14,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.Shamrock"
android:zygotePreloadName="@string/app_name"
android:multiArch="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
@ -47,7 +44,7 @@
android:value="基于 Xposed 实现 OneBot 标准的 QQ 机器人框架" />
<meta-data
android:name="xposedminversion"
android:value="23" />
android:value="93" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope" />

View File

@ -9,52 +9,61 @@ inline void replace_string(std::string& str, const std::string& from, const std:
}
}
inline int utf8_next_len(const std::string& str, size_t offset)
{
uint8_t c = (uint8_t)str[offset];
if (c >= 0xFC)
return 6;
else if (c >= 0xF8)
return 5;
else if (c >= 0xF0)
return 4;
else if (c >= 0xE0)
return 3;
else if (c >= 0xC0)
return 2;
else if (c > 0x00)
return 1;
else
return 0;
}
void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::string, std::string>>& dest) {
std::string cache;
bool is_start = false;
std::string key_tmp;
std::unordered_map<std::string, std::string> kv;
for(int i = 0; i < code.size(); i++) {
auto c = code[i];
if (c == '[') {
for(size_t i = 0; i < code.size(); i++) {
int utf8_char_len = utf8_next_len(code, i);
if(utf8_char_len == 0) {
continue;
}
std::string_view c(&code[i],utf8_char_len);
if (c == "[") {
if (is_start) {
throw illegal_code();
} else {
if (!cache.empty()) {
std::unordered_map<std::string, std::string> kv;
replace_string(cache, "&#91;", "[");
replace_string(cache, "&#93;", "]");
replace_string(cache, "&amp;", "&");
kv.emplace("_type", "text");
kv.emplace("text", cache);
dest.push_back(kv);
cache.clear();
}
auto c1 = code[i + 1];
if (c1 == 'C') {
i++;
auto c2 = code[i + 1];
if(c2 == 'Q') {
i++;
auto c3 = code[i + 1];
if (c3 == ':') {
i++;
is_start = true;
} else {
cache += c;
cache += c1;
cache += c2;
continue;
}
} else {
cache += c;
cache += c1;
continue;
}
} else {
std::string_view cq_flag(&code[i],4);
if(cq_flag == "[CQ:"){
is_start = true;
i += 3;
}else{
cache += c;
continue;
}
}
}
else if (c == '=') {
else if (c == "=") {
if (is_start) {
if (cache.empty()) {
throw illegal_code();
@ -70,17 +79,17 @@ void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::
cache += c;
}
}
else if (c == ',') {
else if (c == ",") {
if (is_start) {
if (kv.count("_type") == 0 && !cache.empty()) {
kv.emplace("_type", cache);
cache.clear();
} else {
if (!key_tmp.empty()) {
replace_string(cache, "&amp;", "&");
replace_string(cache, "&#91;", "[");
replace_string(cache, "&#93;", "]");
replace_string(cache, "&#44;", ",");
replace_string(cache, "&amp;", "&");
kv.emplace(key_tmp, cache);
cache.clear();
key_tmp.clear();
@ -90,15 +99,17 @@ void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::
cache += c;
}
}
else if (c == ']') {
else if (c == "]") {
if (is_start) {
if (!cache.empty()) {
if (!key_tmp.empty()) {
replace_string(cache, "&amp;", "&");
replace_string(cache, "&#91;", "[");
replace_string(cache, "&#93;", "]");
replace_string(cache, "&#44;", ",");
replace_string(cache, "&amp;", "&");
kv.emplace(key_tmp, cache);
} else {
kv.emplace("_type", cache);
}
dest.push_back(kv);
kv.clear();
@ -112,10 +123,14 @@ void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::
}
else {
cache += c;
i += (utf8_char_len - 1);
}
}
if (!cache.empty()) {
std::unordered_map<std::string, std::string> kv;
replace_string(cache, "&#91;", "[");
replace_string(cache, "&#93;", "]");
replace_string(cache, "&amp;", "&");
kv.emplace("_type", "text");
kv.emplace("text", cache);
dest.push_back(kv);

View File

@ -37,6 +37,26 @@ Java_moe_fuqiuluo_shamrock_utils_MD5_genFileMd5Hex(JNIEnv *env, jobject thiz, js
return env->NewStringUTF(md5Hex.c_str());
}
extern "C"
JNIEXPORT jbyteArray JNICALL
Java_moe_fuqiuluo_shamrock_utils_MD5_genFileMd5(JNIEnv *env, jobject thiz, jstring file_path) {
auto cPathStr = env->GetStringUTFChars(file_path, nullptr);
std::filesystem::path filePath(cPathStr);
if (!std::filesystem::exists(filePath)) {
jclass exClass = env->FindClass("java/io/FileNotFoundException");
env->ThrowNew(exClass, "目标文件不存在");
env->DeleteLocalRef(exClass);
return nullptr;
}
auto file = std::ifstream(filePath.c_str(), std::ios::binary);
MD5 md5;
md5.update(file);
auto md5Bytes = md5.digest();
auto jByteArray = env->NewByteArray(16);
env->SetByteArrayRegion(jByteArray, 0, 16, reinterpret_cast<const jbyte*>(md5Bytes));
return jByteArray;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_moe_fuqiuluo_shamrock_utils_MD5_getMd5Hex(JNIEnv *env, jobject thiz, jbyteArray bytes) {

View File

@ -52,6 +52,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
@ -120,6 +121,7 @@ private fun AppMainView() {
val coreVersion = remember { mutableStateOf(getShamrockVersion(context)) }
val coreName = remember { mutableStateOf("Xposed") }
val voiceSwitch = remember { mutableStateOf(false) }
@Suppress("LocalVariableName") val LocalString = LocalString.init()
if (!AppRuntime.isInit) {
AppRuntime.state = remember {
@ -140,23 +142,22 @@ private fun AppMainView() {
mutableStateOf("2854200454")
}
it.nick = remember {
mutableStateOf("测试昵称")
mutableStateOf(LocalString.testName)
}
}
AppRuntime.requestCount = remember { mutableIntStateOf(0) }
AppRuntime.isInit = false
AppRuntime.isInit = true
}
val ctx = LocalContext.current
@Suppress("LocalVariableName") val LocalString = LocalString
LaunchedEffect(isFined.value) {
if (isFined.value) {
AppRuntime.log("日志框架激活成功,开放操作许可。")
AppRuntime.log(LocalString.logCentralLoadSuccessfully)
Toast.makeText(ctx, LocalString.frameworkYes, Toast.LENGTH_SHORT).show()
} else {
AppRuntime.log("日志框架处于未激活状态,请检查。")
AppRuntime.log(LocalString.logCentralLoadFailed)
Toast.makeText(ctx, LocalString.frameworkNo, Toast.LENGTH_SHORT).show()
}
}

View File

@ -224,6 +224,16 @@ object ShamrockConfig {
preferences.edit().putBoolean("debug", v).apply()
}
fun isAntiTrace(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("anti_qq_trace", true)
}
fun setAntiTrace(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("anti_qq_trace", v).apply()
}
fun isInjectPacket(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("inject_packet", false)
@ -239,6 +249,11 @@ object ShamrockConfig {
return preferences.getBoolean("enable_auto_start", false)
}
fun enableAliveReply(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("alive_reply", false)
}
fun allowShell(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("shell", false)
@ -249,6 +264,11 @@ object ShamrockConfig {
preferences.edit().putBoolean("enable_auto_start", v).apply()
}
fun setAliveReply(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("alive_reply", v).apply()
}
fun setShellStatus(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("shell", v).apply()
@ -259,19 +279,19 @@ object ShamrockConfig {
return preferences.getBoolean("enable_self_msg", false)
}
fun enableSyncMsgAsSentMsg(ctx: Context): Boolean {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("enable_sync_msg_as_sent_msg", false)
}
fun setEnableSelfMsg(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("enable_self_msg", v).apply()
}
fun isEchoNumber(ctx: Context): Boolean {
fun setEnableSyncMsgAsSentMsg(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
return preferences.getBoolean("echo_number", false)
}
fun setEchoNumber(ctx: Context, v: Boolean) {
val preferences = ctx.getSharedPreferences("config", 0)
preferences.edit().putBoolean("echo_number", v).apply()
preferences.edit().putBoolean("enable_sync_msg_as_sent_msg", v).apply()
}
fun getConfigMap(ctx: Context): Map<String, Any?> {
@ -293,12 +313,15 @@ object ShamrockConfig {
"ssl_pwd" to preferences.getString("ssl_pwd", ""),
"inject_packet" to preferences.getBoolean("inject_packet", false),
"debug" to preferences.getBoolean("debug", false),
"auto_clear" to preferences.getBoolean("auto_clear", false),
"anti_qq_trace" to preferences.getBoolean("anti_qq_trace", true),
//"auto_clear" to preferences.getBoolean("auto_clear", false),
"ssl_private_pwd" to preferences.getString("ssl_private_pwd", ""),
"key_store" to preferences.getString("key_store", ""),
"enable_self_msg" to preferences.getBoolean("enable_self_msg", false),
"echo_number" to preferences.getBoolean("echo_number", false),
"shell" to preferences.getBoolean("shell", false),
"alive_reply" to preferences.getBoolean("alive_reply", false),
"enable_sync_msg_as_sent_msg" to preferences.getBoolean("enable_sync_msg_as_sent_msg", false),
)
}

View File

@ -50,6 +50,7 @@ import moe.fuqiuluo.shamrock.ui.app.AppRuntime
import moe.fuqiuluo.shamrock.ui.app.Level
import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig
import moe.fuqiuluo.shamrock.ui.theme.GlobalColor
import moe.fuqiuluo.shamrock.ui.theme.LocalString
import moe.fuqiuluo.shamrock.ui.theme.ThemeColor
import moe.fuqiuluo.shamrock.ui.tools.InputDialog
@ -70,7 +71,7 @@ fun DashboardFragment(
AccountCard(nick, uin)
InformationCard(ctx)
APIInfoCard(ctx)
FunctionCard(scope, ctx, "功能设置")
FunctionCard(scope, ctx, LocalString.functionSetting)
SSLCard(ctx)
}
}
@ -80,7 +81,7 @@ private fun SSLCard(ctx: Context) {
ActionBox(
modifier = Modifier.padding(top = 12.dp),
painter = painterResource(id = R.drawable.baseline_security_24),
title = "SSL配置"
title = LocalString.sslSetting
) {
Column {
Divider(
@ -277,9 +278,7 @@ private fun APIInfoCard(
text = authToken,
hint = "请填写鉴权token",
error = "输入的参数不合法",
checker = {
it.length in 0 .. 36
},
checker = { true },
confirm = {
ShamrockConfig.setToken(ctx, authToken.value)
AppRuntime.log("设置鉴权Token为[${authToken.value}]。")

View File

@ -1,6 +1,6 @@
package moe.fuqiuluo.shamrock.ui.fragment
import android.widget.Toast
import android.content.Context
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.absolutePadding
import androidx.compose.foundation.layout.fillMaxSize
@ -22,6 +22,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import moe.fuqiuluo.shamrock.R
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
import moe.fuqiuluo.shamrock.ui.app.Level
import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig
import moe.fuqiuluo.shamrock.ui.theme.GlobalColor
import moe.fuqiuluo.shamrock.ui.theme.LocalString
@ -46,10 +47,11 @@ fun LabFragment() {
}
NoticeTextDialog(
openDialog = showNoticeDialog,
title = "温馨提示",
text = "实验室功能会导致一些奇怪的问题,请谨慎使用!"
title = LocalString.warnTitle,
text = LocalString.labWarning
)
val LocalString = LocalString
ActionBox(
modifier = Modifier.padding(top = 12.dp),
painter = painterResource(id = R.drawable.baseline_preview_24),
@ -63,19 +65,19 @@ fun LabFragment() {
)
Function(
title = "中二病模式",
desc = "也许会导致奇怪的问题,大抵就是你看不懂罢了。",
title = LocalString.b2Mode,
desc = LocalString.b2ModeDesc,
descColor = it,
isSwitch = ShamrockConfig.is2B(ctx)
) {
ShamrockConfig.set2B(ctx, it)
scope.toast(ctx, "重启生效哦!")
scope.toast(ctx, LocalString.restartToast)
return@Function true
}
Function(
title = "显示调试日志",
desc = "会导致日志刷屏。",
title = LocalString.showDebugLog,
desc = LocalString.showDebugLogDesc,
descColor = it,
isSwitch = ShamrockConfig.isDebug(ctx)
) {
@ -90,7 +92,92 @@ fun LabFragment() {
modifier = Modifier.padding(top = 12.dp),
painter = painterResource(id = R.drawable.round_logo_dev_24),
title = "实验功能"
) {
) { color ->
Column {
Divider(
modifier = Modifier,
color = GlobalColor.Divider,
thickness = 0.2.dp
)
/*
Function(
title = "自动清理QQ垃圾",
desc = "也许会导致奇怪的问题(无效)。",
descColor = color,
isSwitch = ShamrockConfig.isAutoClean(ctx)
) {
ShamrockConfig.setAutoClean(ctx, it)
ShamrockConfig.pushUpdate(ctx)
return@Function false
}*/
Function(
title = "自回复测试",
desc = "发送[ping],机器人发送一个具有调试信息的返回。",
descColor = color,
isSwitch = ShamrockConfig.enableAliveReply(ctx)
) {
ShamrockConfig.setAliveReply(ctx, it)
return@Function true
}
Function(
title = "开启Shell接口",
desc = "可能导致设备被入侵,请勿随意开启。",
descColor = color,
isSwitch = ShamrockConfig.allowShell(ctx)
) {
ShamrockConfig.setShellStatus(ctx, it)
return@Function true
}
Function(
title = "自动唤醒QQ",
desc = "QQ进程死亡时重新打开QQ进程前提本进程存活。",
descColor = color,
isSwitch = ShamrockConfig.enableAutoStart(ctx)
) {
ShamrockConfig.setAutoStart(ctx, it)
return@Function true
}
kotlin.runCatching {
ctx.getSharedPreferences("shared_config", Context.MODE_WORLD_READABLE)
}.onSuccess {
Function(
title = LocalString.persistentText,
desc = LocalString.persistentTextDesc,
descColor = color,
isSwitch = it.getBoolean("persistent", false)
) { v ->
it.edit().putBoolean("persistent", v).apply()
scope.toast(ctx, LocalString.restartSysToast)
return@Function true
}
Function(
title = "禁用Doze模式",
desc = "禁止系统进入节能模式。",
descColor = color,
isSwitch = it.getBoolean("hook_doze", false)
) { value ->
it.edit().putBoolean("hook_doze", value).apply()
scope.toast(ctx, LocalString.restartSysToast)
return@Function true
}
}.onFailure {
AppRuntime.log("无法启用附加选项LSPosed模块未激活或者不支持XSharedPreferences", Level.WARN)
}
}
}
ActionBox(
modifier = Modifier.padding(top = 12.dp),
painter = painterResource(id = R.drawable.sharp_lock_24),
title = "安全性设置"
) { color ->
Column {
Divider(
modifier = Modifier,
@ -99,20 +186,9 @@ fun LabFragment() {
)
Function(
title = "自动清理QQ垃圾",
desc = "也许会导致奇怪的问题(无效)。",
descColor = it,
isSwitch = ShamrockConfig.isAutoClean(ctx)
) {
ShamrockConfig.setAutoClean(ctx, it)
ShamrockConfig.pushUpdate(ctx)
return@Function false
}
Function(
title = "拦截QQ无用收包",
desc = "测试阶段,可能导致网络异常或掉线。",
descColor = it,
title = LocalString.injectPacket,
desc = LocalString.injectPacketDesc,
descColor = color,
isSwitch = ShamrockConfig.isInjectPacket(ctx)
) {
ShamrockConfig.setInjectPacket(ctx, it)
@ -121,26 +197,31 @@ fun LabFragment() {
}
Function(
title = "自动唤醒QQ",
desc = "QQ进程死亡时重新打开QQ进程前提本进程存活。",
descColor = it,
isSwitch = ShamrockConfig.enableAutoStart(ctx)
title = LocalString.antiTrace,
desc = LocalString.antiTraceDesc,
descColor = color,
isSwitch = ShamrockConfig.isAntiTrace(ctx)
) {
ShamrockConfig.setAutoStart(ctx, it)
ShamrockConfig.setAntiTrace(ctx, it)
ShamrockConfig.pushUpdate(ctx)
return@Function true
}
Function(
title = "开启Shell接口",
desc = "可能导致设备被入侵,请勿随意开启。",
descColor = it,
isSwitch = ShamrockConfig.allowShell(ctx)
) {
ShamrockConfig.setShellStatus(ctx, it)
return@Function true
kotlin.runCatching {
ctx.getSharedPreferences("shared_config", Context.MODE_WORLD_READABLE)
}.onSuccess {
Function(
title = "反检测加强",
desc = "可能导致某些设备频繁闪退",
descColor = color,
isSwitch = it.getBoolean("super_anti", false)
) { v ->
it.edit().putBoolean("super_anti", v).apply()
scope.toast(ctx, LocalString.restartToast)
return@Function true
}
}
}
}
ActionBox(
@ -161,7 +242,11 @@ fun LabFragment() {
descColor = it,
isSwitch = AppRuntime.state.supportVoice.value
) {
if(AppRuntime.state.supportVoice.value) {
scope.toast(ctx, "关闭请手动删除文件。")
} else {
scope.toast(ctx, "请按照Github提示手动操作。")
}
return@Function false
}
}
@ -190,6 +275,17 @@ fun LabFragment() {
return@Function true
}
Function(
title = "同步消息推送类型异换",
desc = "推送来自同号异设备消息,将同步消息作为自发消息推送。",
descColor = it,
isSwitch = ShamrockConfig.enableSyncMsgAsSentMsg(ctx)
) {
ShamrockConfig.setEnableSyncMsgAsSentMsg(ctx, it)
ShamrockConfig.pushUpdate(ctx)
return@Function true
}
/*
Function(
title = "使用纯数字ECHO",

View File

@ -8,6 +8,7 @@ import android.os.Bundle
import androidx.core.content.ContextCompat.startActivity
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.parameter
import io.ktor.client.request.url
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
@ -58,7 +59,8 @@ object DashboardInitializer {
url("http://127.0.0.1:$servicePort/get_account_info")
val token = ShamrockConfig.getToken(context)
if (token.isNotBlank()) {
header("Authorization", "Bearer $token")
//header("Authorization", "Bearer $token")
parameter("access_token", token)
}
}.let {
if (it.status == HttpStatusCode.OK) {

View File

@ -6,7 +6,9 @@ package moe.fuqiuluo.shamrock.ui.theme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import moe.fuqiuluo.shamrock.R
private val LocalStringDefault = Default()
@ -69,6 +71,24 @@ private open class Chūnibyō: Default() {
"执明起,至除免于灾祸。\n" +
"元冥浩浩,非凡不可动之。"
labWarning = "寒酥降矣,梅熟日久,莫不可测。"
logTitle = "无极"
testName = "未名之人"
logCentralLoadSuccessfully = "无极开,天地始纷争。"
logCentralLoadFailed = "无极闭,天地始归宁。"
functionSetting = "天地法则"
sslSetting = "天行御令"
warnTitle = "仙人指路"
b2Mode = "通仙之路"
b2ModeDesc = "凡人勿近"
restartToast = "复关喏哉!"
showDebugLog = "窥探天机"
showDebugLogDesc = "迷失自我,走火入魔"
antiTrace = "鬼影迷踪"
antiTraceDesc = "唐门绝学,已有取死之道"
injectPacket = "遮匿无用之禀"
injectPacketDesc = "试于试之,逆则魂飞魄散"
persistentText = "丹书铁券"
persistentTextDesc = "由天地之起也,须复动之。"
}
}
@ -84,7 +104,25 @@ private open class Default: VarString(
"同时声明本项目仅用于学习与交流请于24小时内删除。\n" +
"同时开源贡献者均享受免责条例。",
labWarning = "实验室功能可能会导致出乎意料的BUG!",
"日志"
logTitle = "日志",
testName = "测试昵称",
logCentralLoadSuccessfully = "日志框架激活成功,开放操作许可。",
logCentralLoadFailed = "日志框架处于未激活状态,请检查。",
functionSetting = "功能设置",
sslSetting = "SSL配置",
warnTitle = "温馨提示",
b2Mode = "中二病模式",
b2ModeDesc = "也许会导致奇怪的问题,大抵就是你看不懂罢了。",
restartToast = "重启生效哦!",
restartSysToast = "重启系统生效哦!",
showDebugLog = "显示调试日志",
showDebugLogDesc = "会导致日志刷屏。",
antiTrace = "防止调用栈检测",
antiTraceDesc = "防止QQ进行堆栈跟踪检测需要重新启动QQ。",
injectPacket = "拦截QQ无用收包",
injectPacketDesc = "测试阶段,可能导致网络异常或掉线。",
persistentText = "免死金牌",
persistentTextDesc = "由系统复活QQ和Shamrock需要重新启动系统。"
)
open class VarString(
@ -99,5 +137,43 @@ open class VarString(
var labWarning: String,
var logTitle: String
)
var logTitle: String,
var testName: String,
var logCentralLoadSuccessfully: String,
var logCentralLoadFailed: String,
var functionSetting: String,
var sslSetting: String,
var warnTitle: String,
var b2Mode: String,
var b2ModeDesc: String,
var restartToast: String,
var restartSysToast: String,
var showDebugLog: String,
var showDebugLogDesc: String,
var antiTrace: String,
var antiTraceDesc: String,
var injectPacket: String,
var injectPacketDesc: String,
var persistentText: String,
var persistentTextDesc: String
) {
private var inited = false
@Composable
fun init(): VarString {
if (inited) return this
inited = true
return this
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#9D9D9D"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,8h-3L17,6.21c0,-2.61 -1.91,-4.94 -4.51,-5.19C9.51,0.74 7,3.08 7,6v2L4,8v14h16L20,8zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM9,8L9,6c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2L9,8z"/>
</vector>

View File

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.1.2" apply false
id("com.android.application") version "8.2.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
id("com.android.library") version "8.1.2" apply false
id("com.android.library") version "8.2.0" apply false
//id("io.realm.kotlin") version "1.11.0" apply false
}

Binary file not shown.

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStorePath=wrapper/dists

3
gradlew vendored
View File

@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

View File

@ -5,10 +5,10 @@ plugins {
android {
namespace = "moe.fuqiuluo.qqinterface"
compileSdk = 33
compileSdk = 34
defaultConfig {
minSdk = 24
minSdk = 27
consumerProguardFiles("consumer-rules.pro")
}

View File

@ -33,7 +33,7 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor {
return null;
}
public SharedPreferences.Editor putBoolean(String str, boolean z) {
public SharedPreferences.Editor putBoolean(String s, boolean z) {
return this;
}

View File

@ -0,0 +1,7 @@
package com.tencent.mobileqq.filemanager.api;
import com.tencent.mobileqq.qroute.QRouteApi;
public interface IFileManagerUtil extends QRouteApi {
byte[] getSHA(String str);
}

View File

@ -21,6 +21,10 @@ public abstract class PBField<T> {
return new PBBytesField(byteStringMicro, false);
}
public static PBFloatField initFloat(float paramFloat) {
return new PBFloatField(paramFloat, false);
}
public static PBBoolField initBool(boolean z) {
return new PBBoolField(z, false);
}

View File

@ -0,0 +1,13 @@
package com.tencent.mobileqq.pb;
public class PBFloatField extends PBPrimitiveField<Float> {
public PBFloatField(float i2, boolean z) {
}
public int get() {
return 0;
}
public void set(int i2) {
}
}

View File

@ -0,0 +1,51 @@
package com.tencent.mobileqq.transfile;
public class HttpNetReq extends NetReq {
public static final int HTTP_GET = 0;
public static final int HTTP_POST = 1;
public IFlowDecoder decoder;
public DnsParseCallback mDnsParseCallback;
public boolean mHaveIpConnect;
public String mHostForHttpsVerify;
public int mHttpMethod;
public boolean mIsHostIP;
public boolean mIsHttps;
public boolean mIsPreStructPic;
public boolean mIsSync;
public boolean mNeedIpConnect;
public boolean mNeedNotReferer;
public boolean mNeedRedirectCallback;
public String mReqUrl;
public TimeoutParam mTimeoutParam;
public boolean mUseCmwapConnectionTypeFromDpc;
public String[] mWhiteListContentType;
public interface DecoderState {
public static final int STATE_END = 2;
public static final int STATE_INIT = 0;
public static final int STATE_RUNNING = 1;
}
public interface DnsParseCallback {
void end();
void start();
}
public interface IFlowDecoder {
byte[] decode(byte[] bArr);
boolean isFinish();
void reset();
}
public HttpNetReq() {
this.mHttpMethod = 0;
this.mNeedIpConnect = false;
this.mHaveIpConnect = false;
this.mNeedRedirectCallback = false;
this.mUseCmwapConnectionTypeFromDpc = false;
this.mNeedNotReferer = false;
}
}

View File

@ -0,0 +1,7 @@
package com.tencent.mobileqq.transfile;
public interface INetEngineListener {
void onResp(NetResp netResp);
void onUpdateProgeress(NetReq netReq, long j2, long j3);
}

View File

@ -0,0 +1,5 @@
package com.tencent.mobileqq.transfile;
public interface NetFailedListener {
void onFailed(NetResp netResp);
}

View File

@ -0,0 +1,89 @@
package com.tencent.mobileqq.transfile;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
public class NetReq {
public static final int PRIOTY_HIGH = 0;
public static final int PRIOTY_LOW = 2;
public static final int PRIOTY_NORMAL = 1;
public boolean bAcceptNegativeContentLength;
public int mBusiProtoType;
public INetEngineListener mCallback;
public boolean mCanPrintUrl;
public int mContinuErrorLimit;
public int mContinueConnReusedErrorLimit;
public long mDelayTime;
public long mEndDownOffset;
public long mExcuteTimeLimit;
public NetFailedListener mFailedListener;
public int mFileType;
public HostParseToIp mHostParseToIp;
public boolean mIsNetChgAsError;
public boolean mIsRenameInEngine;
public String mKey;
public String mMsgId;
public String mOutPath;
public OutputStream mOutStream;
public int mPrioty;
public Object mPrivate;
public HashMap<String, String> mReqProperties;
public NetResp mResp;
public byte[] mSendData;
public List<ServerAddr> mServerList;
public long mStartDownOffset;
public boolean mSupportBreakResume;
public String mTempPath;
public boolean mUseByteArrayPool;
public boolean mUseRaf;
private Object mUserData;
public long taskStartTime;
public interface HostParseToIp {
List<ServerAddr> getIpByHost(String str);
}
public NetReq() {
this.mStartDownOffset = 0L;
this.mEndDownOffset = 0L;
this.mIsRenameInEngine = true;
this.mDelayTime = 0L;
this.mExcuteTimeLimit = 480000L;
this.mContinuErrorLimit = 8;
this.mContinueConnReusedErrorLimit = 5;
this.mIsNetChgAsError = false;
this.mPrioty = 1;
this.mResp = null;
this.mCanPrintUrl = true;
this.bAcceptNegativeContentLength = true;
this.mUseByteArrayPool = false;
this.mKey = null;
this.taskStartTime = -1L;
this.mReqProperties = new HashMap<>();
}
public long getTaskCostTime() {
return System.currentTimeMillis() - taskStartTime;
}
public synchronized Object getUserData() {
return mUserData;
}
public boolean isWriteToFile() {
return this.mOutPath != null;
}
public boolean isWriteToStream() {
return this.mOutStream != null;
}
public boolean saveRecvDataInTransLayer() {
return this.mOutPath != null || this.mOutStream != null;
}
public synchronized void setUserData(Object obj) {
this.mUserData = obj;
}
}

View File

@ -0,0 +1,72 @@
package com.tencent.mobileqq.transfile;
import java.util.HashMap;
public class NetResp {
public static final String KEY_FIRST_USE_IP = "firstserverip";
public static final String KEY_RAW_REQ_HTTP_HEADER = "param_reqHeader";
public static final String KEY_RAW_RESP_HTTP_HEADER = "param_rspHeader";
public static final String KEY_REASON = "netresp_param_reason";
public static final String KEY_USE_SERVER_IP = "serverip";
public static final String KEY_USE_URL = "param_url";
public static final int RESULT_DOWNLOADING = 3;
public static final int RESULT_FAIL = 1;
public static final int RESULT_NOT_SET = 2;
public static final int RESULT_OK = 0;
public long inQueueCost;
public long mConsumeTime;
public int mErrCode;
public String mErrDesc;
public int mHttpCode;
public long mLastReqStartTime;
public int mRedirectCount;
public long mRedirectTime;
public NetReq mReq;
public byte[] mRespData;
public HashMap<String, String> mRespProperties;
public int mResult;
public long mTotalBlockLen;
public long mTotalFileLen;
public int mTryTime;
public long mWrittenBlockLen;
public long reqCost;
public NetResp(NetReq netReq) {
this.mResult = 2;
this.mTotalFileLen = 0L;
this.mTotalBlockLen = 0L;
this.mWrittenBlockLen = 0L;
this.mConsumeTime = 0L;
this.mTryTime = 0;
this.mRespProperties = new HashMap<>();
this.mRedirectCount = 0;
this.mRedirectTime = 0L;
this.reqCost = 0L;
this.inQueueCost = 0L;
this.mReq = netReq;
this.mLastReqStartTime = System.currentTimeMillis();
}
public void reset() {
this.mResult = 2;
this.mErrCode = 0;
this.mErrDesc = "";
this.mHttpCode = 0;
this.mTotalFileLen = 0L;
this.mTotalBlockLen = 0L;
this.mWrittenBlockLen = 0L;
this.mConsumeTime = 0L;
this.mTryTime = 0;
this.mRespData = null;
this.mRespProperties.clear();
}
public void setResult(int i2, int i3, String str, HashMap<String, String> hashMap) {
this.mResult = i2;
this.mErrCode = i3;
this.mErrDesc = str;
if (hashMap != null) {
this.mRespProperties.putAll(hashMap);
}
}
}

View File

@ -0,0 +1,72 @@
package com.tencent.mobileqq.transfile;
public class TimeoutParam {
public static final int TIMEOUT_STEP = 2000;
private int connectTimeoutBias;
public int connectTimeoutFor2G;
public int connectTimeoutFor3G;
public int connectTimeoutForWifi;
public int readTimeoutFor2G;
public int readTimeoutFor3G;
public int readTimeoutForWifi;
public TimeoutParam() {
this.readTimeoutFor2G = 40000;
this.readTimeoutFor3G = 30000;
this.readTimeoutForWifi = 20000;
this.connectTimeoutFor2G = 20000;
this.connectTimeoutFor3G = 15000;
this.connectTimeoutForWifi = 10000;
this.connectTimeoutBias = 0;
}
public void adjustConnectTimeout(int i2) {
this.connectTimeoutBias = i2 * 2000;
}
public int getConnectTimeout(int i2) {
if (i2 != 1) {
if (i2 == 3) {
return this.connectTimeoutFor3G + this.connectTimeoutBias;
}
if (i2 != 4 && i2 != 5) {
return this.connectTimeoutFor2G + this.connectTimeoutBias;
}
}
return this.connectTimeoutForWifi + this.connectTimeoutBias;
}
public int getReadTimeout(int i2) {
if (i2 != 1) {
if (i2 == 3) {
return this.readTimeoutFor3G;
}
if (i2 != 4 && i2 != 5) {
return this.readTimeoutFor2G;
}
}
return this.readTimeoutForWifi;
}
//public TimeoutParam clone() {
// IPatchRedirector iPatchRedirector = $redirector_;
// return (iPatchRedirector == null || !iPatchRedirector.hasPatch((short) 4)) ? new TimeoutParam(this) : (TimeoutParam) iPatchRedirector.redirect((short) 4, (Object) this);
//}
TimeoutParam(TimeoutParam timeoutParam) {
this.readTimeoutFor2G = 40000;
this.readTimeoutFor3G = 30000;
this.readTimeoutForWifi = 20000;
this.connectTimeoutFor2G = 20000;
this.connectTimeoutFor3G = 15000;
this.connectTimeoutForWifi = 10000;
this.connectTimeoutBias = 0;
this.readTimeoutFor2G = timeoutParam.readTimeoutFor2G;
this.readTimeoutFor3G = timeoutParam.readTimeoutFor3G;
this.readTimeoutForWifi = timeoutParam.readTimeoutForWifi;
this.connectTimeoutFor2G = timeoutParam.connectTimeoutFor2G;
this.connectTimeoutFor3G = timeoutParam.connectTimeoutFor3G;
this.connectTimeoutForWifi = timeoutParam.connectTimeoutForWifi;
}
}

View File

@ -0,0 +1,15 @@
package com.tencent.mobileqq.transfile.api;
import com.tencent.mobileqq.transfile.NetReq;
import com.tencent.mobileqq.transfile.NetResp;
import mqq.app.api.IRuntimeService;
@Deprecated
public interface IHttpEngineService extends IRuntimeService {
void cancelReq(NetReq netReq);
void sendReq(NetReq netReq);
NetResp sendReqSync(NetReq netReq);
}

View File

@ -0,0 +1,14 @@
package com.tencent.mobileqq.transfile.api;
import com.tencent.mobileqq.qroute.QRouteApi;
import com.tencent.mobileqq.transfile.NetReq;
public interface IOldHttpEngineProcessor extends QRouteApi {
//void cancelMsg(HttpMsg httpMsg);
void cancelReq(NetReq netReq);
//int sendMsg(HttpMsg httpMsg);
void sendReq(NetReq netReq);
}

View File

@ -0,0 +1,4 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class GuildInteractiveNotificationItem {
}

View File

@ -0,0 +1,4 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class GuildNotificationAbstractInfo {
}

View File

@ -42,6 +42,10 @@ public interface IKernelMsgListener {
void onGroupTransferInfoUpdate(GroupFileListResult groupFileListResult);
void onGuildInteractiveUpdate(GuildInteractiveNotificationItem guildInteractiveNotificationItem);
void onGuildNotificationAbstractUpdate(GuildNotificationAbstractInfo guildNotificationAbstractInfo);
void onHitCsRelatedEmojiResult(DownloadRelateEmojiResultInfo downloadRelateEmojiResultInfo);
void onHitEmojiKeywordResult(HitRelatedEmojiWordsResult hitRelatedEmojiWordsResult);

View File

@ -18,7 +18,6 @@ public final class MarkdownElement {
}
public MarkdownElement(String str) {
this.content = "";
this.content = str;
}
}

View File

@ -3,6 +3,7 @@ package mqq.manager;
import android.content.Context;
import oicq.wlogin_sdk.request.Ticket;
import oicq.wlogin_sdk.request.WtTicketPromise;
public interface TicketManager extends Manager {
String getA2(String uin);
@ -15,6 +16,8 @@ public interface TicketManager extends Manager {
String getPskey(String uin, String domain);
Ticket getPskey(String str, long j2, String[] strArr, WtTicketPromise wtTicketPromise);
String getPt4Token(String uin, String domain);
String getSkey(String uin); // 假的Skey

View File

@ -0,0 +1,11 @@
package oicq.wlogin_sdk.request;
import oicq.wlogin_sdk.tools.ErrMsg;
public interface WtTicketPromise {
void Done(Ticket ticket);
void Failed(ErrMsg errMsg);
void Timeout(ErrMsg errMsg);
}

View File

@ -0,0 +1,118 @@
package oicq.wlogin_sdk.tools;
import android.os.Parcel;
import android.os.Parcelable;
public class ErrMsg implements Cloneable, Parcelable {
public static final Parcelable.Creator<ErrMsg> CREATOR = new Parcelable.Creator<ErrMsg>() { // from class: oicq.wlogin_sdk.tools.ErrMsg.1
@Override // android.os.Parcelable.Creator
public ErrMsg createFromParcel(Parcel parcel) {
return new ErrMsg(parcel);
}
@Override // android.os.Parcelable.Creator
public ErrMsg[] newArray(int i2) {
return new ErrMsg[i2];
}
};
private String message;
private String otherinfo;
private String title;
private int type;
private int version;
public ErrMsg() {
this.version = 0;
this.type = 0;
//this.title = InternationMsg.a(InternationMsg.MSG_TYPE.MSG_0);
//this.message = InternationMsg.a(InternationMsg.MSG_TYPE.MSG_1);
this.otherinfo = "";
}
public ErrMsg(int i2, int i3, String str, String str2, String str3) {
this.version = i2;
this.type = i3;
this.title = str;
this.message = str2;
this.otherinfo = str3;
}
private ErrMsg(Parcel parcel) {
readFromParcel(parcel);
}
@Override // android.os.Parcelable
public int describeContents() {
return 0;
}
public String getMessage() {
return this.message;
}
public String getOtherinfo() {
return this.otherinfo;
}
public String getTitle() {
return this.title;
}
public int getType() {
return this.type;
}
public int getVersion() {
return this.version;
}
public void readFromParcel(Parcel parcel) {
this.version = parcel.readInt();
this.type = parcel.readInt();
this.title = parcel.readString();
this.message = parcel.readString();
this.otherinfo = parcel.readString();
}
public void setMessage(String str) {
this.message = str;
}
public void setOtherinfo(String str) {
this.otherinfo = str;
}
public void setTitle(String str) {
this.title = str;
}
public void setType(int i2) {
this.type = i2;
}
public void setVersion(int i2) {
this.version = i2;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("(");
int i2 = this.version;
sb.append(i2 < 0 ? Integer.valueOf(i2) : Integer.toString(i2));
sb.append(")(");
int i3 = this.type;
sb.append(i3 < 0 ? Integer.valueOf(i3) : Integer.toString(i3));
sb.append(")[");
String sb2 = sb.toString();
return sb2 + this.title + "]" + this.message + "[" + this.otherinfo + "]";
}
@Override // android.os.Parcelable
public void writeToParcel(Parcel parcel, int i2) {
parcel.writeInt(this.version);
parcel.writeInt(this.type);
parcel.writeString(this.title);
parcel.writeString(this.message);
parcel.writeString(this.otherinfo);
}
}

View File

@ -0,0 +1,326 @@
package tencent.im.group;
import com.tencent.mobileqq.pb.ByteStringMicro;
import com.tencent.mobileqq.pb.MessageMicro;
import com.tencent.mobileqq.pb.PBBoolField;
import com.tencent.mobileqq.pb.PBBytesField;
import com.tencent.mobileqq.pb.PBField;
import com.tencent.mobileqq.pb.PBRepeatField;
import com.tencent.mobileqq.pb.PBRepeatMessageField;
import com.tencent.mobileqq.pb.PBStringField;
import com.tencent.mobileqq.pb.PBUInt32Field;
import com.tencent.mobileqq.pb.PBUInt64Field;
public class group_member_info {
public static class CustomEntry extends MessageMicro<tencent.im.group.group_member_info.CustomEntry> {
static final FieldMap __fieldMap__;
public final PBBoolField bool_clicked;
public final PBBytesField str_name;
public final PBBytesField str_url;
public final PBBytesField str_value;
public final PBUInt64Field uint64_report_id;
static {
ByteStringMicro byteStringMicro = ByteStringMicro.EMPTY;
Boolean bool = Boolean.FALSE;
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 24, 34, 40 }, new String[] { "str_name", "str_value", "bool_clicked", "str_url", "uint64_report_id" }, new Object[] { byteStringMicro, byteStringMicro, bool, byteStringMicro, Long.valueOf(0L) }, tencent.im.group.group_member_info.CustomEntry.class);
}
public CustomEntry() {
ByteStringMicro byteStringMicro = ByteStringMicro.EMPTY;
this.str_name = PBField.initBytes(byteStringMicro);
this.str_value = PBField.initBytes(byteStringMicro);
this.bool_clicked = PBField.initBool(false);
this.str_url = PBField.initBytes(byteStringMicro);
this.uint64_report_id = PBField.initUInt64(0L);
}
}
public static class ErrorInfo extends MessageMicro<tencent.im.group.group_member_info.ErrorInfo> {
static final FieldMap __fieldMap__;
public final PBUInt32Field error_code = PBField.initUInt32(0);
public final PBBytesField error_desc = PBField.initBytes(ByteStringMicro.EMPTY);
static {
ByteStringMicro byteStringMicro = ByteStringMicro.EMPTY;
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 8, 18 }, new String[] { "error_code", "error_desc" }, new Object[] { Integer.valueOf(0), byteStringMicro }, tencent.im.group.group_member_info.ErrorInfo.class);
}
}
public static class FlowersEntry extends MessageMicro<tencent.im.group.group_member_info.FlowersEntry> {
static final FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 8 }, new String[] { "uint64_flower_count" }, new Object[] { Long.valueOf(0L) }, tencent.im.group.group_member_info.FlowersEntry.class);
public final PBUInt64Field uint64_flower_count = PBField.initUInt64(0L);
}
public static class GBarInfo extends MessageMicro<tencent.im.group.group_member_info.GBarInfo> {
static final FieldMap __fieldMap__;
public final PBBytesField bytes_gbar_name;
public final PBBytesField str_head_portrait;
public final PBUInt32Field uint32_gbar_id = PBField.initUInt32(0);
public final PBUInt32Field uint32_uin_lev = PBField.initUInt32(0);
static {
Integer integer = Integer.valueOf(0);
ByteStringMicro byteStringMicro = ByteStringMicro.EMPTY;
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 8, 16, 26, 34 }, new String[] { "uint32_gbar_id", "uint32_uin_lev", "str_head_portrait", "bytes_gbar_name" }, new Object[] { integer, integer, byteStringMicro, byteStringMicro }, tencent.im.group.group_member_info.GBarInfo.class);
}
public GBarInfo() {
ByteStringMicro byteStringMicro = ByteStringMicro.EMPTY;
this.str_head_portrait = PBField.initBytes(byteStringMicro);
this.bytes_gbar_name = PBField.initBytes(byteStringMicro);
}
}
public static class MemberGameInfo extends MessageMicro<tencent.im.group.group_member_info.MemberGameInfo> {
static final FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 26, 34, 42, 50, 58 }, new String[] { "str_game_name", "str_level_name", "str_level_icon", "str_game_font_color", "str_game_background_color", "str_game_url", "str_desc_info" }, new Object[] { "", "", "", "", "", "", "" }, tencent.im.group.group_member_info.MemberGameInfo.class);
public final PBRepeatField<String> str_desc_info = PBField.initRepeat(PBField.initString(""));
public final PBStringField str_game_background_color = PBField.initString("");
public final PBStringField str_game_font_color = PBField.initString("");
public final PBStringField str_game_name = PBField.initString("");
public final PBStringField str_game_url = PBField.initString("");
public final PBStringField str_level_icon = PBField.initString("");
public final PBStringField str_level_name = PBField.initString("");
}
public static class MemberInfo extends MessageMicro<tencent.im.group.group_member_info.MemberInfo> {
public static final int CONCERN_TYPE_CONCERN = 1;
public static final int CONCERN_TYPE_GENERAL = 0;
public static final int CONCERN_TYPE_HATE = 2;
static final FieldMap __fieldMap__;
public final PBBoolField bool_is_allow_mod_card;
public final PBBoolField bool_is_concerned;
public final PBBoolField bool_is_friend;
public final PBBoolField bool_is_super_qq;
public final PBBoolField bool_is_super_vip;
public final PBBoolField bool_is_vip;
public final PBBoolField bool_is_year_vip;
public final PBBoolField bool_location_shared;
public final PBBytesField bytes_group_honor;
public final PBBytesField bytes_job;
public final PBBytesField bytes_phone_num;
public final PBBytesField bytes_special_title;
public final PBUInt32Field medal_id;
public tencent.im.group.group_member_info.FlowersEntry msg_flower_entry;
public tencent.im.group.group_member_info.MemberGameInfo msg_game_info;
public group_member_info.TeamEntry msg_team_entry;
// public group_member_info.RspGroupCardGetStory qqstory_infocard;
public final PBRepeatMessageField<tencent.im.group.group_member_info.CustomEntry> rpt_msg_custom_enties;
public final PBRepeatMessageField<tencent.im.group.group_member_info.GBarInfo> rpt_msg_gbar_concerned;
public final PBBytesField str_card;
public final PBBytesField str_errmsg;
public final PBBytesField str_gbar_title;
public final PBBytesField str_gbar_url;
public final PBBytesField str_lev;
public final PBBytesField str_location;
public final PBBytesField str_nick;
public final PBBytesField str_remark;
public final PBUInt32Field uint32_age;
public final PBUInt32Field uint32_concern_type;
public final PBUInt32Field uint32_credit;
public final PBUInt32Field uint32_gbar_cnt;
public final PBUInt32Field uint32_group_honor_bit;
public final PBUInt32Field uint32_level;
public final PBUInt32Field uint32_result = PBField.initUInt32(0);
public final PBUInt32Field uint32_role;
public final PBUInt32Field uint32_sex;
public final PBUInt32Field uint32_special_title_expire_time;
public final PBUInt32Field uint32_vip_lev;
public final PBUInt64Field uint64_distance;
public final PBUInt64Field uint64_join;
public final PBUInt64Field uint64_last_speak;
public final PBUInt64Field uint64_uin = PBField.initUInt64(0L);
static {
Long long_ = Long.valueOf(0L);
Integer integer = Integer.valueOf(0);
ByteStringMicro byteStringMicro = ByteStringMicro.EMPTY;
Boolean bool = Boolean.FALSE;
__fieldMap__ = MessageMicro.initFieldMap(new int[] {
8, 16, 26, 32, 42, 48, 56, 66, 72, 82,
90, 96, 106, 112, 120, 130, 138, 146, 154, 160,
168, 176, 184, 192, 200, 208, 216, 224, 232, 240,
250, 256, 266, 274, 282, 290, 296, 306, 312, 322,
330, 336 }, new String[] {
"uint64_uin", "uint32_result", "str_errmsg", "bool_is_friend", "str_remark", "bool_is_concerned", "uint32_credit", "str_card", "uint32_sex", "str_location",
"str_nick", "uint32_age", "str_lev", "uint64_join", "uint64_last_speak", "rpt_msg_custom_enties", "rpt_msg_gbar_concerned", "str_gbar_title", "str_gbar_url", "uint32_gbar_cnt",
"bool_is_allow_mod_card", "bool_is_vip", "bool_is_year_vip", "bool_is_super_vip", "bool_is_super_qq", "uint32_vip_lev", "uint32_role", "bool_location_shared", "uint64_distance", "uint32_concern_type",
"bytes_special_title", "uint32_special_title_expire_time", "msg_flower_entry", "msg_team_entry", "bytes_phone_num", "bytes_job", "medal_id", "qqstory_infocard", "uint32_level", "msg_game_info",
"bytes_group_honor", "uint32_group_honor_bit" }, new Object[] {
long_, integer, byteStringMicro, bool, byteStringMicro, bool, integer, byteStringMicro, integer, byteStringMicro,
byteStringMicro, integer, byteStringMicro, long_, long_, null, null, byteStringMicro, byteStringMicro, integer,
bool, bool, bool, bool, bool, integer, integer, bool, long_, integer,
byteStringMicro, integer, null, null, byteStringMicro, byteStringMicro, integer, null, integer, null,
byteStringMicro, integer }, tencent.im.group.group_member_info.MemberInfo.class);
}
public MemberInfo() {
ByteStringMicro byteStringMicro = ByteStringMicro.EMPTY;
this.str_errmsg = PBField.initBytes(byteStringMicro);
this.bool_is_friend = PBField.initBool(false);
this.str_remark = PBField.initBytes(byteStringMicro);
this.bool_is_concerned = PBField.initBool(false);
this.uint32_credit = PBField.initUInt32(0);
this.str_card = PBField.initBytes(byteStringMicro);
this.uint32_sex = PBField.initUInt32(0);
this.str_location = PBField.initBytes(byteStringMicro);
this.str_nick = PBField.initBytes(byteStringMicro);
this.uint32_age = PBField.initUInt32(0);
this.str_lev = PBField.initBytes(byteStringMicro);
this.uint64_join = PBField.initUInt64(0L);
this.uint64_last_speak = PBField.initUInt64(0L);
this.rpt_msg_custom_enties = PBField.initRepeatMessage(tencent.im.group.group_member_info.CustomEntry.class);
this.rpt_msg_gbar_concerned = PBField.initRepeatMessage(tencent.im.group.group_member_info.GBarInfo.class);
this.str_gbar_title = PBField.initBytes(byteStringMicro);
this.str_gbar_url = PBField.initBytes(byteStringMicro);
this.uint32_gbar_cnt = PBField.initUInt32(0);
this.bool_is_allow_mod_card = PBField.initBool(false);
this.bool_is_vip = PBField.initBool(false);
this.bool_is_year_vip = PBField.initBool(false);
this.bool_is_super_vip = PBField.initBool(false);
this.bool_is_super_qq = PBField.initBool(false);
this.uint32_vip_lev = PBField.initUInt32(0);
this.uint32_role = PBField.initUInt32(0);
this.bool_location_shared = PBField.initBool(false);
this.uint64_distance = PBField.initUInt64(0L);
this.uint32_concern_type = PBField.initUInt32(0);
this.bytes_special_title = PBField.initBytes(byteStringMicro);
this.uint32_special_title_expire_time = PBField.initUInt32(0);
this.msg_flower_entry = new tencent.im.group.group_member_info.FlowersEntry();
this.msg_team_entry = new group_member_info.TeamEntry();
this.bytes_phone_num = PBField.initBytes(byteStringMicro);
this.bytes_job = PBField.initBytes(byteStringMicro);
this.medal_id = PBField.initUInt32(0);
// this.qqstory_infocard = new group_member_info.RspGroupCardGetStory();
this.uint32_level = PBField.initUInt32(0);
this.msg_game_info = new tencent.im.group.group_member_info.MemberGameInfo();
this.bytes_group_honor = PBField.initBytes(byteStringMicro);
this.uint32_group_honor_bit = PBField.initUInt32(0);
}
}
public static class ReqBody extends MessageMicro<tencent.im.group.group_member_info.ReqBody> {
static final FieldMap __fieldMap__;
public final PBBoolField bool_new_client = PBField.initBool(false);
public final PBUInt32Field uint32_client_type = PBField.initUInt32(0);
public final PBUInt32Field uint32_rich_card_name_ver = PBField.initUInt32(0);
public final PBUInt64Field uint64_group_code = PBField.initUInt64(0L);
public final PBUInt64Field uint64_uin = PBField.initUInt64(0L);
static {
Long long_ = Long.valueOf(0L);
Integer integer = Integer.valueOf(0);
Boolean bool = Boolean.FALSE;
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 8, 16, 24, 32, 40 }, new String[] { "uint64_group_code", "uint64_uin", "bool_new_client", "uint32_client_type", "uint32_rich_card_name_ver" }, new Object[] { long_, long_, bool, integer, integer }, tencent.im.group.group_member_info.ReqBody.class);
}
}
public static class RspBody extends MessageMicro<RspBody> {
static final FieldMap __fieldMap__;
public final PBBoolField bool_self_location_shared = PBField.initBool(false);
public MemberInfo msg_meminfo = new MemberInfo();
public final PBUInt32Field uint32_group_type = PBField.initUInt32(0);
public final PBUInt32Field uint32_self_role = PBField.initUInt32(0);
public final PBUInt64Field uint64_group_code = PBField.initUInt64(0L);
static {
Integer integer = Integer.valueOf(0);
Boolean bool = Boolean.FALSE;
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 8, 16, 26, 32, 40 }, new String[] { "uint64_group_code", "uint32_self_role", "msg_meminfo", "bool_self_location_shared", "uint32_group_type" }, new Object[] { Long.valueOf(0L), integer, null, bool, integer }, tencent.im.group.group_member_info.RspBody.class);
}
}
public static class TeamEntry extends MessageMicro<TeamEntry> {
static final FieldMap __fieldMap__;
public final PBRepeatField<Long> rpt_uint64_depid;
public final PBRepeatField<Long> rpt_uint64_self_depid;
static {
Long long_ = Long.valueOf(0L);
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 8, 16 }, new String[] { "rpt_uint64_depid", "rpt_uint64_self_depid" }, new Object[] { long_, long_ }, tencent.im.group.group_member_info.TeamEntry.class);
}
public TeamEntry() {
PBUInt64Field pBUInt64Field = (PBUInt64Field) PBUInt64Field.__repeatHelper__;
this.rpt_uint64_depid = PBField.initRepeat((PBField)pBUInt64Field);
this.rpt_uint64_self_depid = PBField.initRepeat((PBField)pBUInt64Field);
}
}
}

View File

@ -0,0 +1,51 @@
package tencent.im.oidb.cmd0x8a7;
import com.tencent.mobileqq.pb.ByteStringMicro;
import com.tencent.mobileqq.pb.MessageMicro;
import com.tencent.mobileqq.pb.PBBoolField;
import com.tencent.mobileqq.pb.PBBytesField;
import com.tencent.mobileqq.pb.PBField;
import com.tencent.mobileqq.pb.PBUInt32Field;
import com.tencent.mobileqq.pb.PBUInt64Field;
public class cmd0x8a7 {
public static class ReqBody
extends MessageMicro<ReqBody>
{
static final MessageMicro.FieldMap __fieldMap__;
public final PBUInt32Field uint32_limit_interval_type_for_group = PBField.initUInt32(0);
public final PBUInt32Field uint32_limit_interval_type_for_uin = PBField.initUInt32(0);
public final PBUInt32Field uint32_sub_cmd = PBField.initUInt32(0);
public final PBUInt64Field uint64_group_code = PBField.initUInt64(0L);
public final PBUInt64Field uint64_uin = PBField.initUInt64(0L);
static
{
Integer localInteger = Integer.valueOf(0);
Long localLong = Long.valueOf(0L);
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 8, 16, 24, 32, 40 }, new String[] { "uint32_sub_cmd", "uint32_limit_interval_type_for_uin", "uint32_limit_interval_type_for_group", "uint64_uin", "uint64_group_code" }, new Object[] { localInteger, localInteger, localInteger, localLong, localLong }, ReqBody.class);
}
}
public static class RspBody
extends MessageMicro<RspBody>
{
static final MessageMicro.FieldMap __fieldMap__;
public final PBBoolField bool_can_at_all = PBField.initBool(false);
public final PBBoolField bool_show_at_all_lable = PBField.initBool(false);
public final PBBytesField bytes_prompt_msg_1 = PBField.initBytes(ByteStringMicro.EMPTY);
public final PBBytesField bytes_prompt_msg_2 = PBField.initBytes(ByteStringMicro.EMPTY);
public final PBUInt32Field uint32_remain_at_all_count_for_group = PBField.initUInt32(0);
public final PBUInt32Field uint32_remain_at_all_count_for_uin = PBField.initUInt32(0);
static
{
Integer localInteger = Integer.valueOf(0);
Boolean localBoolean = Boolean.valueOf(false);
ByteStringMicro localByteStringMicro1 = ByteStringMicro.EMPTY;
ByteStringMicro localByteStringMicro2 = ByteStringMicro.EMPTY;
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 8, 16, 24, 34, 42, 48 }, new String[] { "bool_can_at_all", "uint32_remain_at_all_count_for_uin", "uint32_remain_at_all_count_for_group", "bytes_prompt_msg_1", "bytes_prompt_msg_2", "bool_show_at_all_lable" }, new Object[] { localBoolean, localInteger, localInteger, localByteStringMicro1, localByteStringMicro2, localBoolean }, RspBody.class);
}
}
}

View File

@ -0,0 +1,245 @@
package tencent.im.oidb.cmd0xeb7;
import com.tencent.mobileqq.pb.MessageMicro;
import com.tencent.mobileqq.pb.PBEnumField;
import com.tencent.mobileqq.pb.PBField;
import com.tencent.mobileqq.pb.PBFloatField;
import com.tencent.mobileqq.pb.PBInt32Field;
import com.tencent.mobileqq.pb.PBInt64Field;
import com.tencent.mobileqq.pb.PBRepeatField;
import com.tencent.mobileqq.pb.PBRepeatMessageField;
import com.tencent.mobileqq.pb.PBStringField;
public class oidb_0xeb7 {
public static class ReqBody extends MessageMicro<ReqBody> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18 }, new String[] { "signInStatusReq", "signInWriteReq" }, new Object[] { null, null }, ReqBody.class);
public StSignInStatusReq signInStatusReq = new StSignInStatusReq();
public StSignInWriteReq signInWriteReq = new StSignInWriteReq();
}
public static class RspBody extends MessageMicro<RspBody>
{
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18 }, new String[] { "signInStatusRsp", "signInWriteRsp" }, new Object[] { null, null }, RspBody.class);
public StSignInStatusRsp signInStatusRsp = new StSignInStatusRsp();
public StSignInWriteRsp signInWriteRsp = new StSignInWriteRsp();
}
public static class StSignInWriteRsp
extends MessageMicro<StSignInWriteRsp>
{
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 26 }, new String[] { "ret", "doneInfo", "groupScore" }, new Object[] { null, null, null }, StSignInWriteRsp.class);
public SignInStatusDoneInfo doneInfo = new SignInStatusDoneInfo();
public SignInStatusGroupScore groupScore = new SignInStatusGroupScore();
public Ret ret = new Ret();
}
public static class StSignInStatusRsp
extends MessageMicro<StSignInStatusRsp>
{
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 26, 34, 42, 50, 58, 66 }, new String[] { "ret", "base", "yesterday", "notInfo", "doneInfo", "groupScore", "mantleUrl", "backgroundUrl" }, new Object[] { null, null, null, null, null, null, "", "" }, StSignInStatusRsp.class);
public final PBStringField backgroundUrl = PBField.initString("");
public SignInStatusBase base = new SignInStatusBase();
public SignInStatusDoneInfo doneInfo = new SignInStatusDoneInfo();
public SignInStatusGroupScore groupScore = new SignInStatusGroupScore();
public final PBStringField mantleUrl = PBField.initString("");
public SignInStatusNotInfo notInfo = new SignInStatusNotInfo();
public Ret ret = new Ret();
public SignInStatusYesterdayFirst yesterday = new SignInStatusYesterdayFirst();
}
public static class SignInStatusYesterdayFirst
extends MessageMicro<SignInStatusYesterdayFirst>
{
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 26 }, new String[] { "yesterdayFirstUid", "yesterdayWord", "yesterdayNick" }, new Object[] { "", "", "" }, SignInStatusYesterdayFirst.class);
public final PBStringField yesterdayFirstUid = PBField.initString("");
public final PBStringField yesterdayNick = PBField.initString("");
public final PBStringField yesterdayWord = PBField.initString("");
}
public static class SignInStatusNotInfo
extends MessageMicro<SignInStatusNotInfo>
{
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 26 }, new String[] { "buttonWord", "signDescWordLeft", "signDescWordRight" }, new Object[] { "", "", "" }, SignInStatusNotInfo.class);
public final PBStringField buttonWord = PBField.initString("");
public final PBStringField signDescWordLeft = PBField.initString("");
public final PBStringField signDescWordRight = PBField.initString("");
}
public static class SignInStatusGroupScore
extends MessageMicro<SignInStatusGroupScore>
{
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18 }, new String[] { "groupScoreWord", "scoreUrl" }, new Object[] { "", "" }, SignInStatusGroupScore.class);
public final PBStringField groupScoreWord = PBField.initString("");
public final PBStringField scoreUrl = PBField.initString("");
}
public static class SignInStatusDoneInfo
extends MessageMicro<SignInStatusDoneInfo>
{
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 26, 34 }, new String[] { "leftTitleWrod", "rightDescWord", "belowPortraitWords", "recordUrl" }, new Object[] { "", "", "", "" }, SignInStatusDoneInfo.class);
public final PBRepeatField<String> belowPortraitWords = PBField.initRepeat(PBField.initString(""));
public final PBStringField leftTitleWrod = PBField.initString("");
public final PBStringField recordUrl = PBField.initString("");
public final PBStringField rightDescWord = PBField.initString("");
}
public static class StSignInRecordDaySigned extends MessageMicro<StSignInRecordDaySigned> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 13, 16, 26, 34 }, new String[] { "daySignedRatio", "dayTotalSignedUid", "daySignedPage", "daySignedUrl" }, new Object[] { Float.valueOf(0.0F), Integer.valueOf(0), null, "" }, StSignInRecordDaySigned.class);
public StDaySignedPage daySignedPage = new StDaySignedPage();
public final PBFloatField daySignedRatio = PBField.initFloat(0.0F);
public final PBStringField daySignedUrl = PBField.initString("");
public final PBInt32Field dayTotalSignedUid = PBField.initInt32(0);
}
public static class SignInStatusBase extends MessageMicro<SignInStatusBase> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 8, 16 }, new String[] { "status", "currentTimeStamp" }, new Object[] { Integer.valueOf(0), Long.valueOf(0L) }, SignInStatusBase.class);
public final PBInt64Field currentTimeStamp = PBField.initInt64(0L);
public final PBEnumField status = PBField.initEnum(0);
}
public static class StSignInRecordRsp extends MessageMicro<StSignInRecordRsp> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 26, 34, 42, 50 }, new String[] { "ret", "base", "userRecord", "daySigned", "kingRecord", "level" }, new Object[] { null, null, null, null, null, null }, StSignInRecordRsp.class);
public SignInStatusBase base = new SignInStatusBase();
public StSignInRecordDaySigned daySigned = new StSignInRecordDaySigned();
public StSignInRecordKing kingRecord = new StSignInRecordKing();
public StViewGroupLevel level = new StViewGroupLevel();
public Ret ret = new Ret();
public StSignInRecordUser userRecord = new StSignInRecordUser();
}
public static class Ret extends MessageMicro<Ret> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 8, 18 }, new String[] { "code", "msg" }, new Object[] { Integer.valueOf(0), "" }, Ret.class);
public final PBEnumField code = PBField.initEnum(0);
public final PBStringField msg = PBField.initString("");
}
public static class StSignInRecordUser extends MessageMicro<StSignInRecordUser> {
static final MessageMicro.FieldMap __fieldMap__;
public final PBInt64Field continueSignedDays = PBField.initInt64(0L);
public final PBInt64Field earliestSignedTimeStamp = PBField.initInt64(0L);
public final PBStringField groupName = PBField.initString("");
public final PBRepeatField<String> historySignedDays = PBField.initRepeat(PBField.initString(""));
public final PBInt32Field totalSignedDays = PBField.initInt32(0);
static {
Long long_ = Long.valueOf(0L);
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 16, 24, 32, 42, 50 }, new String[] { "totalSignedDays", "earliestSignedTimeStamp", "continueSignedDays", "historySignedDays", "groupName" }, new Object[] { Integer.valueOf(0), long_, long_, "", "" }, StSignInRecordUser.class);
}
}
public static class StDaySignedInfo extends MessageMicro<StDaySignedInfo> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 24, 32 }, new String[] { "uid", "uidGroupNick", "signedTimeStamp", "signInRank" }, new Object[] { "", "", Long.valueOf(0L), Integer.valueOf(0) }, StDaySignedInfo.class);
public final PBInt32Field signInRank = PBField.initInt32(0);
public final PBInt64Field signedTimeStamp = PBField.initInt64(0L);
public final PBStringField uid = PBField.initString("");
public final PBStringField uidGroupNick = PBField.initString("");
}
public static class StDaySignedPage extends MessageMicro<StDaySignedPage> {
static final MessageMicro.FieldMap __fieldMap__;
public final PBRepeatMessageField<StDaySignedInfo> infos = PBField.initRepeatMessage(StDaySignedInfo.class);
public final PBInt32Field offset = PBField.initInt32(0);
public final PBInt32Field total = PBField.initInt32(0);
static {
Integer integer = Integer.valueOf(0);
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 16, 24 }, new String[] { "infos", "offset", "total" }, new Object[] { null, integer, integer }, StDaySignedPage.class);
}
}
public static class StKingSignedInfo extends MessageMicro<StKingSignedInfo> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 24, 32 }, new String[] { "uid", "groupNick", "signedTimeStamp", "signedCount" }, new Object[] { "", "", Long.valueOf(0L), Integer.valueOf(0) }, StKingSignedInfo.class);
public final PBStringField groupNick = PBField.initString("");
public final PBInt32Field signedCount = PBField.initInt32(0);
public final PBInt64Field signedTimeStamp = PBField.initInt64(0L);
public final PBStringField uid = PBField.initString("");
}
public static class StSignInStatusReq extends MessageMicro<StSignInStatusReq> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 24, 34 }, new String[] { "uid", "groupId", "scene", "clientVersion" }, new Object[] { "", "", Integer.valueOf(0), "" }, StSignInStatusReq.class);
public final PBStringField clientVersion = PBField.initString("");
public final PBStringField groupId = PBField.initString("");
public final PBEnumField scene = PBField.initEnum(0);
public final PBStringField uid = PBField.initString("");
}
public static class StSignInWriteReq extends MessageMicro<StSignInWriteReq> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 26 }, new String[] { "uid", "groupId", "clientVersion" }, new Object[] { "", "", "" }, StSignInWriteReq.class);
public final PBStringField clientVersion = PBField.initString("");
public final PBStringField groupId = PBField.initString("");
public final PBStringField uid = PBField.initString("");
}
public static class StViewGroupLevel extends MessageMicro<StViewGroupLevel> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18 }, new String[] { "title", "url" }, new Object[] { "", "" }, StViewGroupLevel.class);
public final PBStringField title = PBField.initString("");
public final PBStringField url = PBField.initString("");
}
public static class StSignInRecordKing extends MessageMicro<StSignInRecordKing> {
static final MessageMicro.FieldMap __fieldMap__ = MessageMicro.initFieldMap(new int[] { 10, 18, 26, 34 }, new String[] { "yesterdayFirst", "topSignedTotal", "topSignedContinue", "kingUrl" }, new Object[] { null, null, null, "" }, StSignInRecordKing.class);
public final PBStringField kingUrl = PBField.initString("");
public final PBRepeatMessageField<StKingSignedInfo> topSignedContinue = PBField.initRepeatMessage(StKingSignedInfo.class);
public final PBRepeatMessageField<StKingSignedInfo> topSignedTotal = PBField.initRepeatMessage(StKingSignedInfo.class);
public StKingSignedInfo yesterdayFirst = new StKingSignedInfo();
}
}

View File

@ -0,0 +1,21 @@
package tencent.im.troop.honor;
import com.tencent.mobileqq.pb.MessageMicro;
import com.tencent.mobileqq.pb.PBField;
import com.tencent.mobileqq.pb.PBRepeatField;
import com.tencent.mobileqq.pb.PBUInt32Field;
public class troop_honor {
public static class GroupUserCardHonor extends MessageMicro<GroupUserCardHonor> {
static final FieldMap __fieldMap__;
public final PBRepeatField<Integer> id = PBField.initRepeat((PBField) PBUInt32Field.__repeatHelper__);
public final PBUInt32Field level = PBField.initUInt32(0);
static {
Integer integer = Integer.valueOf(0);
__fieldMap__ = MessageMicro.initFieldMap(new int[] { 8, 16 }, new String[] { "id", "level" }, new Object[] { integer, integer }, GroupUserCardHonor.class);
}
}
}

View File

@ -7,7 +7,7 @@ pluginManagement {
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
google()
mavenCentral()

View File

@ -8,12 +8,13 @@ plugins {
android {
namespace = "moe.fuqiuluo.xposed"
compileSdk = 33
compileSdk = 34
defaultConfig {
minSdk = 24
minSdk = 27
consumerProguardFiles("consumer-rules.pro")
@Suppress("UnstableApiUsage")
externalNativeBuild {
cmake {
cppFlags += ""
@ -96,5 +97,11 @@ dependencies {
//ksp("androidx.room:room-compiler:$roomVersion")
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:$roomVersion")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.06.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
}

View File

@ -0,0 +1 @@
libclover.so

View File

@ -1,38 +1,17 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("xposed")
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project("clover")
include_directories(helper)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
xposed.cpp)
anti_detection/anti_detection.cpp
helper/jnihelper.cpp
clover.cpp)
# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log)

View File

@ -0,0 +1,6 @@
#include "anti_detection.h"

View File

@ -0,0 +1,26 @@
#ifndef SHAMROCK_ANTI_DETECTION_H
#define SHAMROCK_ANTI_DETECTION_H
#include <vector>
#include <string>
#include <initializer_list>
#include "lsposed.h"
#include "jnihelper.h"
static std::vector<std::string> qemu_detect_props = {
"init.svc.qemu-props", "qemu.hw.mainkeys", "qemu.sf.fake_camera", "ro.kernel.android.qemud",
"qemu.sf.lcd_density", "init.svc.qemud", "ro.kernel.qemu",
"libc.debug.malloc"
};
static int (*backup_system_property_get)(const char *name, char *value);
static FILE* (*backup_fopen)(const char *filename, const char *mode);
static int (*backup_memcmp)(const void* __lhs, const void* __rhs, size_t __n);
//static const char* (*backup_strstr)(const char* h, const char* n);
//int fake_system_property_get(const char *name, char *value);
//FILE* fake_fopen(const char *filename, const char *mode);
//void on_library_loaded(const char *name, void *handle);
#endif //SHAMROCK_ANTI_DETECTION_H

View File

@ -0,0 +1,189 @@
#include <jni.h>
#include "anti_detection/anti_detection.h"
#include "helper/lsposed.h"
#include "jnihelper.h"
static JavaVM *global_jvm = nullptr;
static HookFunType hook_function = nullptr;
extern "C" [[gnu::visibility("default")]] [[gnu::used]]
jint JNI_OnLoad(JavaVM *jvm, void*) {
global_jvm = jvm;
int attach = 0;
JNIEnv *env = JNIHelper::getJNIEnv(jvm, &attach);
// do something
LOGI("[Shamrock] JNI_OnLoad NativeModule Init: %p", env);
if (attach == 1) {
JNIHelper::delJNIEnv(jvm);
}
//hook_function((void *)env->functions->FindClass, (void *)fake_FindClass, (void **)&backup_FindClass);
return JNI_VERSION_1_6;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_moe_fuqiuluo_shamrock_xposed_XposedEntry_00024Companion_injected(JNIEnv *env, jobject thiz) {
LOGI("[Shamrock] injected: %p", hook_function);
return hook_function != nullptr;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_moe_fuqiuluo_shamrock_xposed_XposedEntry_00024Companion_hasEnv(JNIEnv *env, jobject thiz) {
LOGI("[Shamrock] hasEnv: %p", global_jvm);
return global_jvm != nullptr;
}
int fake_system_property_get(const char *name, char *value) {
for (auto &prop: qemu_detect_props) {
if (strstr(name, prop.c_str())) {
LOGI("[Shamrock] bypass qemu detection");
value[0] = 0;
return 0;
}
}
if (strstr(name, "ro.debuggable")
|| strstr(name, "ro.kernel.qemu.gles")
|| strstr(name, "debug.atrace.tags.enableflags")) {
strcpy(value, "0");
return 1;
}
if (strstr(name, "ro.product.cpu.abilist")) {
int len = backup_system_property_get(name, value);
if (len > 0) {
if (strstr(value, "x86")) {
strcpy(value, "arm64-v8a,armeabi-v7a,armeabi");
return 29;
}
}
return len;
}
if (strstr(name, "ro.hardware")) {
int len = backup_system_property_get(name, value);
if (len > 0) {
if (strstr(value, "generic")
|| strstr(value, "unknown")
|| strstr(value, "emulator")
|| strstr(value, "vbox")
|| strstr(value, "nox") //部分NoxAppPlayer
|| strstr(value, "genymotion")
|| strstr(value, "goldfish")) {
strcpy(value, "qcom");
return 4;
}
}
return len;
}
//LOGI("[Shamrock] fake_system_property_get(%s)", name);
return backup_system_property_get(name, value);
}
FILE* fake_fopen(const char *filename, const char *mode) {
if (strstr(filename, "qemu_pipe")) {
LOGI("[Shamrock] bypass qemu detection");
return nullptr;
}
const char* emuSpecFile[] = {
"libhoudini.so",
"libndk.so",
"libnoxd.so", //NoxAppPlayer
"libnoxspeedup.so", //NoxAppPlayer
"nox-prop", //NoxAppPlayer (MayUseless?)
"nox-vbox-sf", //NoxAppPlayer (MayUseless?)
"noxd", //NoxAppPlayer (MayUseless?)
"noxspeedup", //NoxAppPlayer (MayUseless?)
};
for (const char* keyword : emuSpecFile) {
if (strstr(filename, keyword)) {
LOGI("[Shamrock] bypass emu detection");
return nullptr;
}
}
if (strstr(filename, "libdobby.so")) {
LOGI("[Shamrock] bypass dobby detection");
return nullptr;
}
return backup_fopen(filename, mode);
}
char * __cdecl my_strstr(const char *lhs, const char *rhs) {
char *cur = (char *)lhs;
char *l;
char *r;
if (!*rhs) {
return ((char *)lhs);
}
while (*cur) {
l = cur;
r = (char *)rhs;
while (*r && !(*l - *r)) {
l++;
r++;
}
if (!*r) {
return cur;
}
cur++;
}
return nullptr;
}
int fake_memcmp(const void* __lhs, const void* __rhs, size_t __n) {
//if (my_strstr((const char*) __rhs, "lsposed")) {
//return -1;
//}
//if (my_strstr((const char*) __rhs, "xposed")) {
// return -1;
//}
if (my_strstr((const char*) __rhs, "shamrock")) {
if (backup_memcmp(__lhs, __rhs, __n) == 0) {
// 底层广播判断
return 0;
}
return -1;
}
if (my_strstr((const char*) __rhs, "riru")) {
return -1;
}
//if (my_strstr((const char*) __rhs, "zygisk")) {
// return -1;
//}
//if (my_strstr((const char*) __rhs, "magisk")) {
// return -1;
//}
return backup_memcmp(__lhs, __rhs, __n);
}
void on_library_loaded(const char *name, void *handle) {
}
extern "C" [[gnu::visibility("default")]] [[gnu::used]]
NativeOnModuleLoaded native_init(const NativeAPIEntries *entries) {
hook_function = entries->hook_func;
LOGI("[Shamrock] LSPosed NativeModule Init: %p", hook_function);
return on_library_loaded;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_moe_fuqiuluo_shamrock_xposed_actions_AntiDetection_antiNativeDetections(JNIEnv *env,
jobject thiz) {
if (hook_function == nullptr) return false;
hook_function((void*) __system_property_get, (void *)fake_system_property_get, (void **) &backup_system_property_get);
hook_function((void*) fopen, (void*) fake_fopen, (void**) &backup_fopen);
//hook_function((void*) strstr, (void*) fake_strstr, (void**) &backup_strstr);
hook_function((void*) memcmp, (void*) fake_memcmp, (void**) &backup_memcmp);
return true;
}

View File

@ -0,0 +1,26 @@
#include "jnihelper.h"
JNIEnv *JNIHelper::getJNIEnv(JavaVM * jvm, int *attach) {
if (jvm == NULL) return NULL;
*attach = 0;
JNIEnv *jni_env = NULL;
int status = jvm->GetEnv((void **)&jni_env, JNI_VERSION_1_6);
if (status == JNI_EDETACHED || jni_env == NULL) {
status = jvm->AttachCurrentThread(&jni_env, NULL);
if (status < 0) {
jni_env = NULL;
} else {
*attach = 1;
}
}
return jni_env;
}
jint JNIHelper::delJNIEnv(JavaVM * jvm) {
if (jvm == nullptr) return 0;
return jvm->DetachCurrentThread();
}

View File

@ -0,0 +1,14 @@
#ifndef SHAMROCK_JNIHELPER_H
#define SHAMROCK_JNIHELPER_H
#include "jni.h"
#include "android/log.h"
namespace JNIHelper {
JNIEnv *getJNIEnv(JavaVM * jvm, int *attach);
jint delJNIEnv(JavaVM * jvm);
}
#endif //SHAMROCK_JNIHELPER_H

View File

@ -0,0 +1,27 @@
#ifndef SHAMROCK_LSPOSED_H
#define SHAMROCK_LSPOSED_H
#include "stdint.h"
#define TAG "LSPosed-Bridge"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
typedef int (*HookFunType)(void *func, void *replace, void **backup);
typedef int (*UnhookFunType)(void *func);
typedef void (*NativeOnModuleLoaded)(const char *name, void *handle);
typedef struct {
uint32_t version;
HookFunType hook_func;
UnhookFunType unhook_func;
} NativeAPIEntries;
typedef NativeOnModuleLoaded (*NativeInit)(const NativeAPIEntries *entries);
#endif //SHAMROCK_LSPOSED_H

View File

@ -1,5 +0,0 @@
#include <jni.h>
#include <string>
#include <utility>
#include <sys/auxv.h>

View File

@ -50,7 +50,7 @@ val ProtoValue.asLong: Long
get() = (this as ProtoNumber).value.toLong()
val ProtoValue.asULong: Long
get() = (this as ProtoNumber).value.toLong() and 0xFFFFFFFFL
get() = (this as ProtoNumber).value.toLong() and Long.MAX_VALUE
val ProtoValue.asMap: ProtoMap
get() = (this as ProtoMap)

View File

@ -61,7 +61,7 @@ class ProtoMap(
var curMap = value
tags.forEachIndexed { index, tag ->
if (index == tags.size - 1) {
return curMap[tag] ?: error("Tag $tag not found")
return curMap[tag] ?: error("pb[${tags.joinToString(", ")}][$index] Tag $tag not found")
}
curMap[tag]?.let { v ->
if (v is ProtoMap) {
@ -69,7 +69,7 @@ class ProtoMap(
} else {
return v
}
} ?: error("Tag $tag not found")
} ?: error("pb[${tags.joinToString(", ")}][$index] Tag $tag not found")
}
error("Instance is not ProtoMap for get(${tags.first()})")
}

View File

@ -109,7 +109,7 @@ internal object CardSvc: BaseSvc() {
val dataService = app
.getRuntimeService(IProfileDataService::class.java, "all")
val card = refreshCardLock.withLock {
suspendCancellableCoroutine<Card?> {
suspendCancellableCoroutine {
app.addObserver(object: ProfileCardObserver() {
override fun onGetProfileCard(success: Boolean, obj: Any) {
app.removeObserver(this)

View File

@ -166,7 +166,11 @@ internal object FileSvc: BaseSvc() {
modifyTime = fileInfo.uint32_modify_time.get(),
downloadTimes = fileInfo.uint32_download_times.get(),
uploadUin = fileInfo.uint64_uploader_uin.get(),
uploadNick = fileInfo.str_uploader_name.get()
uploadNick = fileInfo.str_uploader_name.get(),
md5 = fileInfo.bytes_md5.get().toByteArray().toHexString(),
sha = fileInfo.bytes_sha.get().toByteArray().toHexString(),
// 根本没有
sha3 = fileInfo.bytes_sha3.get().toByteArray().toHexString(),
))
}
else if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FOLDER) {

View File

@ -13,11 +13,9 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import mqq.app.AppRuntime
import mqq.app.MobileQQ
import tencent.mobileim.structmsg.`structmsg$FlagInfo`
import tencent.mobileim.structmsg.`structmsg$ReqSystemMsgNew`
import tencent.mobileim.structmsg.`structmsg$RspSystemMsgNew`
@ -58,7 +56,10 @@ internal object FriendSvc: BaseSvc() {
)
}
suspend fun requestFriendSystemMsgNew(msgNum: Int, latestFriendSeq: Long, latestGroupSeq: Long): List<StructMsg>? {
suspend fun requestFriendSystemMsgNew(msgNum: Int, latestFriendSeq: Long = 0, latestGroupSeq: Long = 0, retryCnt: Int = 3): List<StructMsg>? {
if (retryCnt < 0) {
return ArrayList()
}
val req = `structmsg$ReqSystemMsgNew`()
req.msg_num.set(msgNum)
req.latest_friend_seq.set(latestFriendSeq)
@ -90,10 +91,18 @@ internal object FriendSvc: BaseSvc() {
req.uint32_req_msg_type.set(1)
req.uint32_need_uid.set(1)
val respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Friend", true, req.toByteArray())
?: return ArrayList()
val msg = `structmsg$RspSystemMsgNew`()
msg.mergeFrom(respBuffer.slice(4))
return msg.friendmsgs.get()
return if (respBuffer == null) {
ArrayList()
} else {
try {
val msg = `structmsg$RspSystemMsgNew`()
msg.mergeFrom(respBuffer.slice(4))
return msg.friendmsgs.get()
} catch (err: Throwable) {
requestFriendSystemMsgNew(msgNum, latestFriendSeq, latestGroupSeq, retryCnt - 1)
}
}
}

View File

@ -2,6 +2,7 @@
package moe.fuqiuluo.qqinterface.servlet
import androidx.core.text.HtmlCompat
import com.tencent.common.app.AppInterface
import com.tencent.mobileqq.app.BusinessHandlerFactory
import com.tencent.mobileqq.app.QQAppInterface
@ -13,7 +14,22 @@ import com.tencent.mobileqq.troop.api.ITroopMemberInfoService
import com.tencent.protofile.join_group_link.join_group_link
import com.tencent.qphone.base.remote.ToServiceMsg
import com.tencent.qqnt.kernel.nativeinterface.MemberInfo
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import friendlist.stUinInfo
import io.ktor.client.call.body
import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.contentType
import io.ktor.http.headers
import io.ktor.http.parameters
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
@ -22,22 +38,51 @@ import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.jsonObject
import moe.fuqiuluo.proto.ProtoUtils
import moe.fuqiuluo.proto.asInt
import moe.fuqiuluo.proto.asUtf8String
import moe.fuqiuluo.proto.protobufOf
import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getLongUin
import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getUin
import moe.fuqiuluo.qqinterface.servlet.entries.GroupAtAllRemainInfo
import moe.fuqiuluo.qqinterface.servlet.entries.ProhibitedMemberInfo
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.remote.service.data.EssenceMessage
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncement
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessage
import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessageImage
import moe.fuqiuluo.shamrock.tools.EmptyJsonArray
import moe.fuqiuluo.shamrock.tools.GlobalClient
import moe.fuqiuluo.shamrock.tools.asInt
import moe.fuqiuluo.shamrock.tools.asJsonArrayOrNull
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asLong
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.asStringOrNull
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import moe.fuqiuluo.shamrock.tools.putBuf32Long
import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import mqq.app.MobileQQ
import tencent.im.group.group_member_info
import tencent.im.oidb.cmd0x899.oidb_0x899
import tencent.im.oidb.cmd0x89a.oidb_0x89a
import tencent.im.oidb.cmd0x8a0.oidb_0x8a0
import tencent.im.oidb.cmd0x8a7.cmd0x8a7
import tencent.im.oidb.cmd0x8fc.Oidb_0x8fc
import tencent.im.oidb.cmd0xeb7.oidb_0xeb7
import tencent.im.oidb.oidb_sso
import tencent.im.troop.honor.troop_honor
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.nio.ByteBuffer
@ -60,6 +105,27 @@ internal object GroupSvc: BaseSvc() {
private lateinit var METHOD_REQ_TROOP_MEM_LIST: Method
private lateinit var METHOD_REQ_MODIFY_GROUP_NAME: Method
suspend fun getGroupRemainAtAllRemain (groupId: Long): Result<GroupAtAllRemainInfo> {
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x8a7_0", 2215, 0, cmd0x8a7.ReqBody().apply {
uint32_sub_cmd.set(1)
uint32_limit_interval_type_for_uin.set(2)
uint32_limit_interval_type_for_group.set(1)
uint64_uin.set(getLongUin())
uint64_group_code.set(groupId)
}.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout"))
val body = oidb_sso.OIDBSSOPkg()
body.mergeFrom(buffer.slice(4))
if(body.uint32_result.get() != 0) {
return Result.failure(RuntimeException(body.str_error_msg.get()))
}
val resp = cmd0x8a7.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
return Result.success(GroupAtAllRemainInfo(
canAtAll = resp.bool_can_at_all.get(),
remainAtAllCountForGroup = resp.uint32_remain_at_all_count_for_group.get(),
remainAtAllCountForUin = resp.uint32_remain_at_all_count_for_uin.get()
))
}
suspend fun getProhibitedMemberList(groupId: Long): Result<List<ProhibitedMemberInfo>> {
val buffer = sendOidbAW("OidbSvc.0x899_0", 2201, 0, oidb_0x899.ReqBody().apply {
uint64_group_code.set(groupId)
@ -113,7 +179,7 @@ internal object GroupSvc: BaseSvc() {
var troopList = service.allTroopList
if(refresh || !service.isTroopCacheInited || troopList == null) {
if(!requestGroupList(service)) {
if(!requestGroupInfo(service)) {
return Result.failure(Exception("获取群列表失败"))
} else {
troopList = service.allTroopList
@ -126,15 +192,14 @@ internal object GroupSvc: BaseSvc() {
val service = app
.getRuntimeService(ITroopInfoService::class.java, "all")
var groupInfo = getGroupInfo(groupId)
val groupInfo = getGroupInfo(groupId)
if(refresh || !service.isTroopCacheInited || groupInfo.troopuin.isNullOrBlank()) {
groupInfo = requestGroupList(service, groupId.toLong()).onFailure {
return Result.failure(it)
}.getOrThrow()
return if(refresh || !service.isTroopCacheInited || groupInfo.troopuin.isNullOrBlank()) {
requestGroupInfo(service, groupId.toLong())
} else {
Result.success(groupInfo)
}
return Result.success(groupInfo)
}
suspend fun setGroupUniqueTitle(groupId: String, userId: String, title: String) {
@ -143,7 +208,7 @@ internal object GroupSvc: BaseSvc() {
req.uint64_group_code.set(groupId.toLong())
val memberInfo = Oidb_0x8fc.MemberInfo()
memberInfo.uint64_uin.set(userId.toLong())
memberInfo.bytes_uin_name.set(ByteStringMicro.copyFromUtf8(localMemberInfo.troopnick.ifBlank {
memberInfo.bytes_uin_name.set(ByteStringMicro.copyFromUtf8(localMemberInfo.troopnick.ifEmpty {
localMemberInfo.troopremark.ifNullOrEmpty("")
}))
memberInfo.bytes_special_title.set(ByteStringMicro.copyFromUtf8(title))
@ -284,7 +349,7 @@ internal object GroupSvc: BaseSvc() {
fun getOwner(groupId: String): Long {
val groupInfo = getGroupInfo(groupId)
return groupInfo.troopowneruin.toLong()
return groupInfo.troopowneruin?.toLong() ?: 0
}
fun isOwner(groupId: String): Boolean {
@ -386,6 +451,36 @@ internal object GroupSvc: BaseSvc() {
}
}
}
try {
if (info != null && (info.alias == null || info.alias.isBlank())) {
val req = group_member_info.ReqBody()
req.uint64_group_code.set(groupId.toLong())
req.uint64_uin.set(uin.toLong())
req.bool_new_client.set(true)
req.uint32_client_type.set(1)
req.uint32_rich_card_name_ver.set(1)
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray())
if (respBuffer != null) {
val rsp = group_member_info.RspBody()
rsp.mergeFrom(respBuffer.slice(4))
if (rsp.msg_meminfo.str_location.has()) {
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
}
if (rsp.msg_meminfo.uint32_age.has()) {
info.age = rsp.msg_meminfo.uint32_age.get().toByte()
}
if (rsp.msg_meminfo.bytes_group_honor.has()) {
val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray()
val honor = troop_honor.GroupUserCardHonor()
honor.mergeFrom(honorBytes)
info.level = honor.level.get()
// 10315: medal_id not real group level
}
}
}
} catch (err: Throwable) {
LogCenter.log(err.stackTraceToString(), Level.WARN)
}
return if (info != null) {
Result.success(info)
} else {
@ -465,7 +560,7 @@ internal object GroupSvc: BaseSvc() {
}
}
private suspend fun requestGroupList(
private suspend fun requestGroupInfo(
service: ITroopInfoService
): Boolean {
refreshTroopList()
@ -492,7 +587,7 @@ internal object GroupSvc: BaseSvc() {
throw RuntimeException("AppRuntime cannot cast to AppInterface")
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_LIST_HANDLER)
// void C(boolean foreRefresh, String groupId, String troopcode, int reqType); // RequestedTroopList/refreshMemberListFromServer
// void C(boolean forceRefresh, String groupId, String troopcode, int reqType); // RequestedTroopList/refreshMemberListFromServer
if (!GroupSvc::METHOD_REQ_TROOP_MEM_LIST.isInitialized) {
METHOD_REQ_TROOP_MEM_LIST = businessHandler.javaClass.declaredMethods.first {
it.parameterCount == 4
@ -558,9 +653,9 @@ internal object GroupSvc: BaseSvc() {
METHOD_REQ_MEMBER_INFO_V2.invoke(businessHandler, groupId.toString(), groupUin2GroupCode(groupId).toString(), arrayListOf(memberUin.toString()))
}
private suspend fun requestGroupList(dataService: ITroopInfoService, uin: Long): Result<TroopInfo> {
private suspend fun requestGroupInfo(dataService: ITroopInfoService, uin: Long): Result<TroopInfo> {
val strUin = uin.toString()
val list = withTimeoutOrNull(5000) {
val info = withTimeoutOrNull(5000) {
var troopInfo: TroopInfo?
do {
troopInfo = dataService.getTroopInfo(strUin)
@ -568,8 +663,8 @@ internal object GroupSvc: BaseSvc() {
} while (troopInfo == null || troopInfo.troopuin.isNullOrBlank())
return@withTimeoutOrNull troopInfo
}
return if (list != null) {
Result.success(list)
return if (info != null) {
Result.success(info)
} else {
Result.failure(Exception("获取群列表失败"))
}
@ -609,21 +704,6 @@ internal object GroupSvc: BaseSvc() {
notSee: Boolean? = false,
subType: String
): Result<String>{
// val app = AppRuntimeFetcher.appRuntime
// if (app !is AppInterface)
// throw RuntimeException("AppRuntime cannot cast to AppInterface")
// val service = QRoute.api(IAddFriendTempApi::class.java)
// val action = `structmsg$SystemMsgActionInfo`()
// action.type.set(if (approve != false) 11 else 12)
// action.group_code.set(gid)
// action.msg.set(msg)
// val snInfo = `structmsg$AddFrdSNInfo`()
// snInfo.uint32_not_see_dynamic.set(if (notSee != false) 1 else 0)
//// snInfo.uint32_set_sn.set(0)
// action.addFrdSNInfo.set(snInfo)
// service.sendFriendSystemMsgAction(2, msgSeq * 1000, uin, 1, 2, 30024, 1, action, 0, `structmsg$StructMsg`(), false,
// app
// )
// 实在找不到接口了 发pb吧
val buffer: ByteArray
when (subType) {
@ -654,7 +734,9 @@ internal object GroupSvc: BaseSvc() {
7 to 1,
8 to mapOf(
1 to if (approve != false) 11 else 12,
2 to gid
2 to gid,
50 to msg,
53 to if (notSee != false) 1 else 0
),
9 to 1000
).toByteArray()
@ -670,14 +752,17 @@ internal object GroupSvc: BaseSvc() {
if (result[1, 1].asInt == 0) {
Result.success(result[2].asUtf8String)
} else {
Result.failure(Exception(result[2].asUtf8String))
Result.failure(Exception(result[1, 2].asUtf8String))
}
} else {
Result.failure(Exception("操作失败"))
}
}
suspend fun requestGroupSystemMsgNew(msgNum: Int, latestFriendSeq: Long, latestGroupSeq: Long): List<StructMsg>? {
suspend fun requestGroupSystemMsgNew(msgNum: Int, reqMsgType: Int = 1, latestFriendSeq: Long = 0, latestGroupSeq: Long = 0, retryCnt: Int = 5): List<StructMsg> {
if (retryCnt < 0) {
return ArrayList()
}
val req = ReqSystemMsgNew()
req.msg_num.set(msgNum)
req.latest_friend_seq.set(latestFriendSeq)
@ -706,12 +791,213 @@ internal object GroupSvc: BaseSvc() {
req.is_get_frd_ribbon.set(false)
req.is_get_grp_ribbon.set(false)
req.friend_msg_type_flag.set(1)
req.uint32_req_msg_type.set(1)
req.uint32_req_msg_type.set(reqMsgType)
req.uint32_need_uid.set(1)
val respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Group", true, req.toByteArray())
?: return ArrayList()
val msg = RspSystemMsgNew()
msg.mergeFrom(respBuffer.slice(4))
return msg.groupmsgs.get()
return if (respBuffer == null) {
ArrayList()
} else {
try {
val msg = RspSystemMsgNew()
msg.mergeFrom(respBuffer.slice(4))
return msg.groupmsgs.get().orEmpty()
} catch (err: Throwable) {
requestGroupSystemMsgNew(msgNum, reqMsgType, latestFriendSeq, latestGroupSeq, retryCnt - 1)
}
}
}
@OptIn(ExperimentalSerializationApi::class)
suspend fun getEssenceMessageList(groupId: Long, page: Int = 0, pageSize: Int = 20): Result<List<EssenceMessage>>{
// GlobalClient.get()
val cookie = TicketSvc.getCookie("qun.qq.com")
val bkn = TicketSvc.getBkn(TicketSvc.getRealSkey(TicketSvc.getUin()))
val url = "https://qun.qq.com/cgi-bin/group_digest/digest_list?bkn=${bkn}&group_code=${groupId}&page_start=${page}&page_limit=${pageSize}"
val response = GlobalClient.get(url) {
header("Cookie", cookie)
}
val body = Json.decodeFromStream<JsonElement>(response.body())
if (body.jsonObject["retcode"].asInt == 0) {
val data = body.jsonObject["data"].asJsonObject
val list = data["msg_list"].asJsonArrayOrNull
?: // is_end
return Result.success(ArrayList())
return Result.success(list.map {
val obj = it.jsonObject
val msgSeq = obj["msg_seq"].asInt
val msg = EssenceMessage(
senderId = obj["sender_uin"].asString.toLong(),
senderNick = obj["sender_nick"].asString,
senderTime = obj["sender_time"].asLong,
operatorId = obj["add_digest_uin"].asString.toLong(),
operatorNick = obj["add_digest_nick"].asString,
operatorTime = obj["add_digest_time"].asLong,
messageId = 0,
messageSeq = msgSeq,
messageContent = obj["msg_content"] ?: EmptyJsonArray
)
val mapping = MessageHelper.getMsgMappingBySeq(MsgConstant.KCHATTYPEGROUP, msgSeq)
if (mapping != null) {
msg.messageId = mapping.msgHashId
}
msg
})
} else {
return Result.failure(Exception(body.jsonObject["retmsg"].asStringOrNull))
}
}
@OptIn(ExperimentalSerializationApi::class)
suspend fun getGroupAnnouncements(groupId: Long): Result<List<GroupAnnouncement>>{
val cookie = TicketSvc.getCookie("qun.qq.com")
val bkn = TicketSvc.getBkn(TicketSvc.getRealSkey(TicketSvc.getUin()))
val url = "https://web.qun.qq.com/cgi-bin/announce/get_t_list?bkn=${bkn}&qid=${groupId}&ft=23&s=-1&n=20"
val response = GlobalClient.get(url) {
header("Cookie", cookie)
}
val body = Json.decodeFromStream<JsonElement>(response.body())
if (body.jsonObject["ec"].asInt == 0) {
val list = body.jsonObject["feeds"].asJsonArrayOrNull
?: return Result.success(ArrayList())
return Result.success(list.map {
val obj = it.jsonObject
GroupAnnouncement(
senderId = obj["u"].asLong,
publishTime = obj["pubt"].asLong,
message = GroupAnnouncementMessage(
// text = obj["msg"].asJsonObject["text"].asString,
text = fromHtml(obj["msg"].asJsonObject["text"].asString),
images = obj["msg"].asJsonObject["pics"].asJsonArrayOrNull?.map { pic ->
GroupAnnouncementMessageImage(
id = pic.jsonObject["id"].asString,
width = pic.jsonObject["w"].asString,
height = pic.jsonObject["h"].asString,
)
} ?: ArrayList()
)
)
})
} else {
return Result.failure(Exception(body.jsonObject["em"].asStringOrNull))
}
}
private fun fromHtml(htmlString: String): String {
return HtmlCompat
// 特殊处理&#10;目的是替换为换行符否则会被fromHtml忽略并移除
.fromHtml(htmlString.replace("&#10;", "[shamrockplaceholder]"), HtmlCompat.FROM_HTML_MODE_LEGACY)
.toString()
.replace("[shamrockplaceholder]", "\n")
}
@OptIn(ExperimentalSerializationApi::class)
suspend fun uploadImageTroopNotice(image: String): Result<GroupAnnouncementMessageImage> {
val file = FileUtils.parseAndSave(image)
val cookie = TicketSvc.getCookie("qun.qq.com")
val bkn = TicketSvc.getBkn(TicketSvc.getRealSkey(TicketSvc.getUin()))
val response = GlobalClient.post("https://web.qun.qq.com/cgi-bin/announce/upload_img") {
headers {
header("Cookie", cookie)
}
contentType(ContentType.MultiPart.FormData)
setBody(MultiPartFormDataContent(
// 黑人问号 ktor默认formdata传的tx不认。默认是name=bkn非要写成name="bkn"才认?
formData {
append("filename", "001.png", Headers.build {
append(HttpHeaders.ContentDisposition, "name=\"filename\"")
})
append("source", "troopNotice", Headers.build {
append(HttpHeaders.ContentDisposition, "name=\"source\"")
})
append("bkn", bkn, Headers.build {
append(HttpHeaders.ContentDisposition, "name=\"bkn\"")
})
append("m", "0", Headers.build {
append(HttpHeaders.ContentDisposition, "name=\"m\"")
})
append("pic_up", file.readBytes(), Headers.build {
append(HttpHeaders.ContentType, "image/png")
append(HttpHeaders.ContentDisposition, "name=\"pic_up\" filename=\"001.png\"")
})
}
))
}
val body = Json.decodeFromStream<JsonElement>(response.body())
if (body.jsonObject["ec"].asInt == 0) {
var idJsonStr = body.jsonObject["id"].asStringOrNull
return if (idJsonStr != null) {
idJsonStr = idJsonStr.replace("&quot;", "\"")
val idJson = Json.decodeFromString<JsonElement>(idJsonStr)
LogCenter.log(idJson.toString())
Result.success(GroupAnnouncementMessageImage(
height = idJson.asJsonObject["h"].asString,
width = idJson.asJsonObject["w"].asString,
id = idJson.asJsonObject["id"].asString,
))
} else {
Result.failure(Exception("图片上传失败"))
}
} else {
return Result.failure(Exception(body.jsonObject["em"].asStringOrNull))
}
}
@OptIn(ExperimentalSerializationApi::class)
suspend fun addQunNotice(groupId: Long, text: String, image: GroupAnnouncementMessageImage?) : Result<Boolean> {
val cookie = TicketSvc.getCookie("qun.qq.com")
val bkn = TicketSvc.getBkn(TicketSvc.getRealSkey(TicketSvc.getUin()))
val response = GlobalClient.submitForm(
url = "https://web.qun.qq.com/cgi-bin/announce/add_qun_notice",
formParameters = parameters {
append("qid", groupId.toString())
append("bkn", bkn)
append("text", text)
append("pinned", "0")
append("type", "1")
// todo allow custom settings
append("settings", "{\"is_show_edit_card:\"1,\"tip_window_type\":1,\"confirm_required\":1}")
if (null != image) {
append("pic", image.id)
append("imgWidth", image.width)
append("imgHeight", image.height)
}
},
block = {
headers {
header("Cookie", cookie)
}
}
)
val body = Json.decodeFromStream<JsonElement>(response.body())
return if (body.jsonObject["ec"].asInt == 0) {
Result.success(true)
} else {
Result.failure(Exception(body.jsonObject["em"].asStringOrNull))
}
}
suspend fun groupSign(groupId: Long): Result<String> {
val req = oidb_0xeb7.ReqBody()
val signInWriteReq = oidb_0xeb7.StSignInWriteReq()
signInWriteReq.groupId.set(groupId.toString())
signInWriteReq.uid.set(getUin())
var version = PlatformUtils.getClientVersion(MobileQQ.getContext())
version = version.replace("android", "").trimStart()
signInWriteReq.clientVersion.set(version)
req.signInWriteReq.set(signInWriteReq)
val buffer = sendOidbAW("OidbSvc.0xeb7", 3767, 1, req.toByteArray())
return if (buffer == null) {
Result.failure(Exception("操作失败"))
} else {
val body = oidb_sso.OIDBSSOPkg()
body.mergeFrom(buffer.slice(4))
val rsp = oidb_0xeb7.RspBody()
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
val doneInfo = rsp.signInWriteRsp.doneInfo
LogCenter.log(rsp.toString(), Level.DEBUG)
Result.success("${doneInfo.leftTitleWrod.get()} ${doneInfo.rightDescWord.get()} ${doneInfo.belowPortraitWords.get().joinToString(" ")}")
}
}
}

View File

@ -16,14 +16,13 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.time.withTimeoutOrNull
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.json.JsonArray
import moe.fuqiuluo.proto.protobufOf
import moe.fuqiuluo.shamrock.helper.ContactHelper
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.helper.SendMsgException
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import moe.fuqiuluo.shamrock.xposed.helper.msgService
@ -32,10 +31,6 @@ import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
internal object MsgSvc: BaseSvc() {
fun uploadForwardMsg(): Result<String> {
return Result.failure(Exception("Not implemented"))
}
suspend fun prepareTempChatFromGroup(
groupId: String,
peerId: String
@ -174,22 +169,29 @@ internal object MsgSvc: BaseSvc() {
chatType: Int,
peedId: String,
message: JsonArray,
fromId: String = peedId
): Pair<Long, Int> {
//LogCenter.log(message.toString(), Level.ERROR)
//callback.msgHash = result.second 什么垃圾代码万一cb比你快你不就寄了
fromId: String = peedId,
retryCnt: Int = 3
): Result<Pair<Long, Int>> {
// 主动临时消息
when(chatType) {
when (chatType) {
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
prepareTempChatFromGroup(fromId, peedId).onFailure {
LogCenter.log("主动临时消息,创建临时会话失败。", Level.ERROR)
return -1L to 0
return Result.failure(Exception("主动临时消息,创建临时会话失败。"))
}
}
}
return MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, MessageCallback(peedId, 0), fromId)
val result = MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0))
return if (result.isFailure
&& result.exceptionOrNull()?.javaClass == SendMsgException::class.java
&& retryCnt > 0) {
// 发送失败,可能网络问题出现红色感叹号,重试
// 例如 rich media transfer failed
delay(100)
sendToAio(chatType, peedId, message, fromId, retryCnt - 1)
} else {
result
}
}
suspend fun getMultiMsg(resId: String): Result<List<MsgRecord>> {

View File

@ -0,0 +1,436 @@
package moe.fuqiuluo.qqinterface.servlet
import android.graphics.BitmapFactory
import com.tencent.mobileqq.app.QQAppInterface
import com.tencent.mobileqq.transfile.HttpNetReq
import com.tencent.mobileqq.transfile.INetEngineListener
import com.tencent.mobileqq.transfile.NetReq
import com.tencent.mobileqq.transfile.NetResp
import com.tencent.mobileqq.transfile.ServerAddr
import com.tencent.mobileqq.transfile.api.IHttpEngineService
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.readBytes
import kotlinx.io.core.writeFully
import moe.fuqiuluo.proto.protobufMapOf
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.utils.MD5
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import mqq.manager.TicketManager
import oicq.wlogin_sdk.request.Ticket
import oicq.wlogin_sdk.request.WtTicketPromise
import oicq.wlogin_sdk.tools.ErrMsg
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.ByteBuffer
import kotlin.coroutines.resume
/**
* QQ收藏相关接口
*/
internal object QFavSvc: BaseSvc() {
private val SERVER_LIST_COLLECTOR = listOf(ServerAddr().also {
it.isIpv6 = false
it.mIp = "collector.weiyun.com"
it.port = 80
})
private val SERVER_LIST_PICUP = listOf(ServerAddr().also {
it.isIpv6 = false
it.mIp = "pic.pieceup.qq.com"
it.port = 80
})
private const val VERSION = 12820
private const val APPID = 30244
private const val SUB_APPID = 538116905
private const val MAJOR_VERSION = 8
private const val MINOR_VERSION = 9
private var seq = 1
suspend fun getItemList(
category: Int,
startPos: Int,
pageSize: Int,
): Result<NetResp> {
val data = protobufMapOf {
it[1] = mapOf(
20000 to mapOf(
/**
* "type", "bid", "category", "start_time", "order_type", "start_pos", "page_size", "sync_policy", "req_source"
*/
1 to 0,
2 to 0,
3 to category,
//4 to System.currentTimeMillis() - 1000 * 60,
//4 to System.currentTimeMillis(),
4 to 0,
5 to 0,
6 to startPos,
7 to pageSize,
8 to 0,
9 to 0
)
)
}.toByteArray()
return sendWeiyunReq(20000, data)
}
suspend fun getItemContent(
id: String
): Result<NetResp> {
val data = protobufMapOf {
it[1] = mapOf(
20001 to mapOf(
1 to id
)
)
}.toByteArray()
return sendWeiyunReq(20001, data)
}
suspend fun addImageMsg(
uin: Long,
name: String,
groupId: Long = 0,
groupName: String = "",
picUrl: String,
pid: String,
width: Int, height: Int,
size: Long,
md5: String,
): Result<NetResp> {
val md5Bytes = md5.hex2ByteArray()
val data = protobufMapOf {
it[1] = mapOf(
20009 to mapOf(
1 to mapOf(
1 to 1, // bid
2 to 1, // category
3 to mapOf( // author
1 to if (groupId == 0L) 1 else 2, // type
2 to uin, // num_id
3 to name, // str_id
4 to groupId, // group_id
5 to groupName // group_name
),
4 to System.currentTimeMillis() - 2000, // create_time
5 to System.currentTimeMillis() - 1000, // sequence
7 to """{"recordAudioOnly":false,"audioOnly":false,"fileOnly":false}""",
9 to 0, // original_app_id
10 to 0 // custom_group_id
),
2 to mapOf(
1 to "",
3 to "[图片]",
4 to mapOf(
1 to picUrl,
2 to md5Bytes,
3 to md5,
6 to width,
7 to height,
8 to size,
9 to 0,
11 to pid
),
5 to 1
),
3 to mapOf(
2 to """<img src="$picUrl" />""",
4 to mapOf(
1 to picUrl,
2 to md5Bytes,
3 to md5,
6 to width,
7 to height,
8 to size,
9 to 0,
11 to pid
)
)
)
)
}.toByteArray()
return sendWeiyunReq(20009, data)
}
suspend fun applyUpImageMsg(
uin: Long,
name: String,
groupId: Long = 0,
groupName: String = "",
width: Int, height: Int,
image: File
): Result<NetResp> {
if (!image.exists()) {
return Result.failure(IllegalArgumentException("image file not exists"))
}
val md5 = MD5.genFileMd5(image.absolutePath)
val data = protobufMapOf {
it[1] = mapOf(
20010 to mapOf(
1 to mapOf(
2 to md5,
4 to md5.toHexString(),
10 to mapOf( // author
1 to if (groupId == 0L) 1 else 2, // type
2 to uin, // num_id
3 to name, // str_id
4 to groupId, // group_id
5 to groupName // group_name
),
6 to width, // width
7 to height,
8 to image.length(),
9 to 1, // type
11 to "/storage/emulated/0/DCIM/ShamrockUpload.jpeg" // pic_id
)
)
)
}.toByteArray()
return sendWeiyunReq(20010, data)
}
suspend fun addRichMediaMsg(
uin: Long,
name: String,
groupId: Long = 0,
groupName: String = "",
time: Long = System.currentTimeMillis(),
content: String
): Result<NetResp> {
val data = protobufMapOf {
it[1] = mapOf(
20009 to mapOf(
1 to mapOf(
/**
* 1 => bid
* 2 => category
* 3 => author
* 4 => create_time
* 5 => sequence
* 6 => biz_key
* 7 => biz_data_list
* 8 => share_url
* 9 => original_app_id
* 10 => custom_group_id
* 506 => modify_time
* 507 => qzone_ugc_key
*/
1 to 1, // bid
2 to 1, // category
3 to mapOf( // author
1 to if (groupId == 0L) 1 else 2, // type
2 to uin, // num_id
3 to name, // str_id
4 to groupId, // group_id
5 to groupName // group_name
),
4 to time - 2000, // create_time
5 to time - 1000, // sequence
9 to 0, // original_app_id
10 to 0 // custom_group_id
),
2 to mapOf(
/**
* 1 => title
* 2 => sub_title
* 3 => brief
* 4 => pic_list
* 5 => content_type
* 6 => original_uri
* 7 => publisher
* 8 => rich_media_version
*/
3 to content,
5 to 1
),
3 to mapOf(
/**
* 1 => rich_media
* 2 => raw_data
* 3 => biz_data_list
* 4 => pic_list
* 5 => file_list
*/
2 to content.textToHtml()
)
)
)
}.toByteArray()
return sendWeiyunReq(20009, data)
}
private fun String.textToHtml(): String {
return replace("\n", "<div><br/></div>")
}
suspend fun sendPicUpBlock(
fileSize: Long,
offset: Long,
block: ByteArray,
blockSize: Long,
sha: ByteArray,
pid: String,
outputStream: ByteArrayOutputStream = ByteArrayOutputStream(),
): Result<NetResp> {
return suspendCancellableCoroutine {
val httpNetReq = HttpNetReq()
httpNetReq.userData = null
httpNetReq.mCallback = object: INetEngineListener {
override fun onResp(netResp: NetResp) {
if (netResp.mHttpCode != 200 && netResp.mResult != 0 && netResp.mErrDesc.isNullOrEmpty()) {
netResp.mErrDesc = netResp.mRespProperties["User-ErrMsg"]
}
netResp.mRespData = outputStream.toByteArray().copyOf()
it.resume(Result.success(netResp))
}
override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {}
}
val vi = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getA2(app.currentAccountUin)
//LogCenter.log(pSKey)
httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
httpNetReq.mSendData = BytePacketBuilder().apply {
writeInt(-1412589450)
writeInt(10000)
writeInt(0)
writeInt(sha.size + 16 + blockSize.toInt())
writeShort(0)
writeShort(sha.size.toShort())
writeFully(sha)
writeInt(fileSize.toInt())
writeInt(offset.toInt())
writeInt(blockSize.toInt())
writeFully(block)
}.build().readBytes()
httpNetReq.mOutStream = outputStream
httpNetReq.mStartDownOffset = 0L
httpNetReq.mReqProperties["Shamrock"] = "true"
httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;pid=%s;appid=%d", app.currentAccountUin, 8, vi, pid, APPID)
httpNetReq.mReqProperties["host"] = "pic.pieceup.qq.com"
httpNetReq.mReqProperties["Range"] = "bytes=0-"
httpNetReq.mReqProperties["Content-Length"] = httpNetReq.mSendData.size.toString()
httpNetReq.mReqProperties["Accept-Encoding"] = "gzip"
httpNetReq.mReqProperties["Content-Encoding"] = "gzip"
httpNetReq.mPrioty = 1
httpNetReq.mReqUrl = "https://pic.pieceup.qq.com/"
httpNetReq.mServerList = SERVER_LIST_PICUP
val service = AppRuntimeFetcher.appRuntime
.getRuntimeService(IHttpEngineService::class.java, "qqfav")
service.sendReq(httpNetReq)
}
}
suspend fun sendWeiyunReq(
cmd: Int,
body: ByteArray,
outputStream: ByteArrayOutputStream = ByteArrayOutputStream(),
): Result<NetResp> {
return suspendCancellableCoroutine {
val httpNetReq = HttpNetReq()
httpNetReq.userData = null
httpNetReq.mCallback = object: INetEngineListener {
override fun onResp(netResp: NetResp) {
if (netResp.mHttpCode != 200 && netResp.mResult != 0 && netResp.mErrDesc.isNullOrEmpty()) {
netResp.mErrDesc = netResp.mRespProperties["User-ErrMsg"]
}
netResp.mRespData = outputStream.toByteArray().copyOf()
it.resume(Result.success(netResp))
}
override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {}
}
val pSKey = getWeiYunPSKey()
httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST
httpNetReq.mSendData = DeflateTools.gzip(packData(packHead(cmd, pSKey), body))
httpNetReq.mOutStream = outputStream
httpNetReq.mStartDownOffset = 0L
httpNetReq.mReqProperties["Shamrock"] = "true"
httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;appid=%d", app.currentAccountUin, 27, pSKey, APPID)
httpNetReq.mReqProperties["host"] = "collector.weiyun.com"
httpNetReq.mReqProperties["Range"] = "bytes=0-"
httpNetReq.mReqProperties["Content-Length"] = httpNetReq.mSendData.size.toString()
httpNetReq.mReqProperties["Accept-Encoding"] = "gzip"
httpNetReq.mReqProperties["Content-Encoding"] = "gzip"
httpNetReq.mPrioty = 1
httpNetReq.mReqUrl = "https://collector.weiyun.com/collector.fcg"
httpNetReq.mServerList = SERVER_LIST_COLLECTOR
val service = AppRuntimeFetcher.appRuntime
.getRuntimeService(IHttpEngineService::class.java, "qqfav")
service.sendReq(httpNetReq)
}
}
private fun packHead(cmd: Int, pskey: String): ByteArray {
/**
* 1 => uin
* 2 => seq
* 3 => type
* 4 => cmd
* 5 => appid
* 6 => version
* 7 => nettype
* 8 => clientip
* 9 => encrypt
* 10 => keytype
* 11 => encryptkey
* 14 => major_version
* 15 => minor_version
* 101 => retcode
* 102 => retmsg
* 103 => promptmsg
* 111 => total_space
* 112 => used_space
*/
return protobufMapOf {
it[1] = app.longAccountUin
it[2] = seq++ // seq
it[3] = 1 // type
it[4] = cmd
it[5] = APPID
it[6] = VERSION // VERSION
it[7] = 3 // nettype
it[10] = 27 // keytype
it[11] = pskey
it[14] = MAJOR_VERSION
it[15] = MINOR_VERSION
}.toByteArray()
}
private fun packData(head: ByteArray, body: ByteArray): ByteArray {
val len = 16 + head.size + body.size
val buf = ByteBuffer.allocate(len)
buf.putInt(SUB_APPID)
buf.putShort(1)
buf.putInt(len)
buf.putInt(body.size)
buf.putShort(0)
buf.put(head)
buf.put(body)
return buf.array()
}
private fun getWeiYunPSKey(): String {
val pskey = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager)
.getPskey(app.currentAccountUin, 16L, arrayOf("weiyun.com"), WeiYunPSKeyPromise)
return if (pskey != null) pskey.getPSkey("weiyun.com") else ""
}
private object WeiYunPSKeyPromise: WtTicketPromise {
override fun Done(ticket: Ticket) {
LogCenter.log("Fav: getPskeyPromise: done", Level.DEBUG)
}
override fun Failed(errMsg: ErrMsg) {
LogCenter.log("Fav: getPskeyPromise: failed, $errMsg", Level.DEBUG)
}
override fun Timeout(errMsg: ErrMsg) {
LogCenter.log("Fav: getPskeyPromise: timeout, $errMsg", Level.DEBUG)
}
}
}

View File

@ -74,8 +74,13 @@ internal object TicketSvc: BaseSvc() {
}
suspend fun getCSRF(uin: String, domain: String): String {
// 是不是要用Skey
return getBkn(getPSKey(uin, domain) ?: "")
}
fun getBkn(arg: String): String {
var v: Long = 5381
for (element in getPSKey(uin, domain) ?: "") {
for (element in arg) {
v += (v shl 5 and 2147483647L) + element.code.toLong()
}
return (v and 2147483647L).toString()

View File

@ -27,6 +27,10 @@ data class FileInfo(
@SerialName("download_times") val downloadTimes: Int,
@SerialName("uploader") val uploadUin: Long,
@SerialName("upload_name") val uploadNick: String,
@SerialName("sha") val sha: String,
@SerialName("sha3") val sha3: String,
@SerialName("md5") val md5: String,
)
@Serializable

View File

@ -7,4 +7,11 @@ import kotlinx.serialization.Serializable
internal data class ProhibitedMemberInfo(
@SerialName("user_id") val memberUin: Long,
@SerialName("time") val shutuptimestap: Int
)
@Serializable
internal data class GroupAtAllRemainInfo(
@SerialName("can_at_all") val canAtAll: Boolean,
@SerialName("remain_at_all_count_for_group") val remainAtAllCountForGroup: Int,
@SerialName("remain_at_all_count_for_uin") val remainAtAllCountForUin: Int
)

View File

@ -22,6 +22,9 @@ import com.tencent.qqnt.kernel.nativeinterface.ReplyElement
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo
import com.tencent.qqnt.kernel.nativeinterface.TextElement
import com.tencent.qqnt.kernel.nativeinterface.VideoElement
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import moe.fuqiuluo.qqinterface.servlet.CardSvc
@ -70,6 +73,8 @@ import tencent.im.oidb.cmd0xdc2.oidb_cmd0xdc2
import tencent.im.oidb.oidb_sso
import java.io.File
import kotlin.math.roundToInt
import kotlin.random.Random
import kotlin.random.nextInt
internal typealias IMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
@ -96,9 +101,91 @@ internal object MessageMaker {
"touch" to MessageMaker::createTouchElem,
"weather" to MessageMaker::createWeatherElem,
"json" to MessageMaker::createJsonElem,
"new_dice" to MessageMaker::createNewDiceElem,
"new_rps" to MessageMaker::createNewRpsElem,
"basketball" to MessageMaker::createBasketballElem,
//"node" to MessageMaker::createNodeElem,
//"multi_msg" to MessageMaker::createLongMsgStruct,
)
// private suspend fun createNodeElem(
// chatType: Int,
// msgId: Long,
// peerId: String,
// data: JsonObject
// ): Result<MsgElement> {
// data.checkAndThrow("data")
// SendForwardMessage(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin(), data["content"].asJsonArray)
//
// }
/**\
* msgElement.setFaceElement(new FaceElement());
* msgElement.getFaceElement().setFaceIndex(114);
* msgElement.getFaceElement().setFaceText("/篮球");
* msgElement.getFaceElement().setFaceType(3);
* msgElement.getFaceElement().setPackId("1");
* msgElement.getFaceElement().setStickerId("13");
* msgElement.getFaceElement().setRandomType(1);
* msgElement.getFaceElement().setImageType(1);
* msgElement.getFaceElement().setStickerType(2);
* msgElement.getFaceElement().setSourceType(1);
* msgElement.getFaceElement().setSurpriseId("");
* msgElement.getFaceElement().setResultId(String.valueOf(new Random().nextInt(5) + 1));
*/
private suspend fun createBasketballElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement()
face.faceIndex = 114
face.faceText = "/篮球"
face.faceType = 3
face.packId = "1"
face.stickerId = "13"
face.sourceType = 1
face.stickerType = 2
face.resultId = Random.nextInt(1 .. 5).toString()
face.surpriseId = ""
face.randomType = 1
elem.faceElement = face
return Result.success(elem)
}
private suspend fun createNewRpsElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement()
face.faceIndex = 359
face.faceText = "/包剪锤"
face.faceType = 3
face.packId = "1"
face.stickerId = "34"
face.sourceType = 1
face.stickerType = 2
face.resultId = ""
face.surpriseId = ""
face.randomType = 1
elem.faceElement = face
return Result.success(elem)
}
private suspend fun createNewDiceElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement()
face.faceIndex = 358
face.faceText = "/骰子"
face.faceType = 3
face.packId = "1"
face.stickerId = "33"
face.sourceType = 1
face.stickerType = 2
face.resultId = ""
face.surpriseId = ""
face.randomType = 1
elem.faceElement = face
return Result.success(elem)
}
private suspend fun createJsonElem(
chatType: Int,
msgId: Long,
@ -107,7 +194,20 @@ internal object MessageMaker {
): Result<MsgElement> {
data.checkAndThrow("data")
val jsonStr = data["data"].let {
if (it is JsonObject) it.asJsonObject.toString() else it.asString
if (it is JsonObject) it.asJsonObject.toString() else {
val str = it.asStringOrNull ?: ""
// 检查字符串是否是合法json不然qq会闪退
try {
val element = Json.decodeFromString<JsonElement>(str)
if (element !is JsonObject) {
return Result.failure(Exception("不合法的JSON字符串"))
}
} catch (err: Throwable) {
LogCenter.log(err.stackTraceToString(), Level.ERROR)
return Result.failure(Exception("不合法的JSON字符串"))
}
str
}
}
val element = MsgElement()
element.elementType = MsgConstant.KELEMTYPEARKSTRUCT
@ -174,7 +274,7 @@ internal object MessageMaker {
element.elementType = MsgConstant.KELEMTYPEREPLY
val reply = ReplyElement()
val msgHash = data["id"].asString.toInt()
val msgHash = data["id"].asInt
val mapping = MessageHelper.getMsgMappingByHash(msgHash)
?: return Result.failure(Exception("不存在该消息映射,无法回复消息"))
@ -527,10 +627,16 @@ internal object MessageMaker {
else -> {
val info = GroupSvc.getTroopMemberInfoByUin(peerId, qq, true).onFailure {
LogCenter.log("无法获取群成员信息: $qq", Level.ERROR)
}.getOrThrow()
at.content = "@${info.troopnick
.ifNullOrEmpty(info.friendnick)
.ifNullOrEmpty(qq)}"
}.getOrNull()
if (info != null) {
at.content = "@${
info.troopnick
.ifNullOrEmpty(info.friendnick)
.ifNullOrEmpty(qq)
}"
} else {
at.content = "@${data["name"].asStringOrNull.ifNullOrEmpty(qq)}"
}
at.atType = MsgConstant.ATTYPEONE
at.atNtUid = ContactHelper.getUidByUinAsync(qq.toLong())
}
@ -634,24 +740,26 @@ internal object MessageMaker {
}
private suspend fun createImageElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
data.checkAndThrow("file")
val isOriginal = data["original"].asBooleanOrNull ?: true
val isFlash = data["flash"].asBooleanOrNull ?: false
val file = data["file"].asString.let {
val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase()
var tmpPicFile = if (md5.length == 32) {
val filePath = data["file"].asStringOrNull
val url = data["url"].asStringOrNull
var file: File? = null
if (filePath != null) {
val md5 = filePath.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase()
file = if (md5.length == 32) {
FileUtils.getFile(md5)
} else {
FileUtils.parseAndSave(it)
FileUtils.parseAndSave(filePath)
}
if (!tmpPicFile.exists() && data.containsKey("url")) {
tmpPicFile = FileUtils.parseAndSave(data["url"].asString)
}
return@let tmpPicFile
}
if (!file.exists()) {
if ((file == null || !file.exists()) && url != null) {
file = FileUtils.parseAndSave(url)
}
if (file?.exists() == false) {
throw LogicException("Image(${file.name}) file is not exists, please check your filename.")
}
requireNotNull(file)
Transfer with when (chatType) {
MsgConstant.KCHATTYPEGROUP -> Troop(peerId)

View File

@ -53,6 +53,7 @@ internal object MessageConvert {
MsgConstant.KELEMTYPEREPLY to MessageElemConverter.ReplyConverter,
MsgConstant.KELEMTYPEGRAYTIP to MessageElemConverter.GrayTipsConverter,
MsgConstant.KELEMTYPEFILE to MessageElemConverter.FileConverter,
MsgConstant.KELEMTYPEMARKDOWN to MessageElemConverter.MarkdownConverter,
//MsgConstant.KELEMTYPEMULTIFORWARD to MessageElemConverter.XmlMultiMsgConverter,
//MsgConstant.KELEMTYPESTRUCTLONGMSG to MessageElemConverter.XmlLongMsgConverter,
)

View File

@ -55,12 +55,42 @@ internal sealed class MessageElemConverter: IMessageConvert {
)
)
}
return MessageSegment(
type = "face",
data = hashMapOf(
"id" to face.faceIndex
when (face.faceIndex) {
114 -> {
return MessageSegment(
type = "basketball",
data = hashMapOf(
"id" to face.resultId.ifEmpty { "0" }.toInt(),
)
)
}
358 -> {
if (face.sourceType == 1) return MessageSegment("new_dice")
return MessageSegment(
type = "new_dice",
data = hashMapOf(
"id" to face.resultId.ifEmpty { "0" }.toInt()
)
)
}
359 -> {
if (face.resultId.isEmpty()) return MessageSegment("new_rps")
return MessageSegment(
type = "new_rps",
data = hashMapOf(
"id" to face.resultId.ifEmpty { "0" }.toInt()
)
)
}
else -> return MessageSegment(
type = "face",
data = hashMapOf(
"id" to face.faceIndex
)
)
)
}
}
}
@ -289,23 +319,24 @@ internal sealed class MessageElemConverter: IMessageConvert {
element: MsgElement
): MessageSegment {
val tip = element.grayTipElement
when(val tipType = tip.subElementType) {
when(tip.subElementType) {
MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> {
val notify = tip.jsonGrayTipElement
when(notify.busiId) {
/* 新人入群 */ 17L,
/* 群戳一戳 */1061L, /* 群撤回 */1014L -> {}
else -> LogCenter.log("不支持的灰条类型(JSON): $tipType", Level.WARN)
/* 新人入群 */ 17L, /* 群戳一戳 */1061L,
/* 群撤回 */1014L, /* 群设精消息 */2401L,
/* 群头衔 */2407L -> {}
else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN)
}
}
MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
val notify = tip.xmlElement
when(notify.busiId) {
/* 群戳一戳 */12L -> {}
else -> LogCenter.log("不支持的灰条类型(XML): $tipType", Level.WARN)
/* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
}
}
else -> LogCenter.log("不支持的提示类型: $tip", Level.WARN)
else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN)
}
// 提示类消息这里提供的是一个xml不具备解析通用性
// 在这里不推送
@ -327,10 +358,10 @@ internal sealed class MessageElemConverter: IMessageConvert {
val fileSize = fileMsg.fileSize
val expireTime = fileMsg.expireTime ?: 0
val fileId = fileMsg.fileUuid
val bizId = fileMsg.fileBizId
val bizId = fileMsg.fileBizId ?: 0
val fileSubId = fileMsg.fileSubId ?: ""
val url = if (chatType == MsgConstant.KCHATTYPEC2C) RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
else RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, fileMsg.fileBizId)
else RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
return MessageSegment(
type = "file",
@ -382,6 +413,22 @@ internal sealed class MessageElemConverter: IMessageConvert {
}
}
object MarkdownConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
element: MsgElement
): MessageSegment {
val markdown = element.markdownElement
return MessageSegment(
type = "markdown",
data = mapOf(
"content" to markdown.content
)
)
}
}
protected fun unknownChatType(chatType: Int) {
throw UnsupportedOperationException("Not supported chat type: $chatType")
}

View File

@ -10,7 +10,7 @@ import kotlin.coroutines.resume
internal object ContactHelper {
suspend fun getUinByUidAsync(uid: String): String {
if (uid.isBlank() || uid == "0") {
return "0"
return "-1"
}
val kernelService = NTServiceFetcher.kernelService
@ -20,7 +20,7 @@ internal object ContactHelper {
sessionService.uixConvertService.getUin(hashSetOf(uid)) {
continuation.resume(it)
}
}[uid]?.toString() ?: "0"
}[uid]?.toString() ?: "-1"
}
suspend fun getUidByUinAsync(peerId: Long): String {

View File

@ -12,3 +12,4 @@ internal class LogicException(why: String) : InternalMessageMakerError(why)
internal object ErrorTokenException : InternalMessageMakerError("access_token error")
internal class SendMsgException(why: String) : InternalMessageMakerError(why)

View File

@ -13,6 +13,7 @@ import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.xposed.actions.toast
import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester
import mqq.app.MobileQQ
import java.io.File
import java.util.Date
internal enum class Level(
@ -26,19 +27,53 @@ internal enum class Level(
@SuppressLint("SimpleDateFormat")
internal object LogCenter {
private val logFileBaseName = MobileQQ.getMobileQQ().qqProcessName.replace(":", ".") + "_${
// 格式化时间
SimpleDateFormat("yyyy-MM-dd").format(Date())
}_"
private val LogFile = MobileQQ.getContext().getExternalFilesDir(null)!!
.parentFile!!.resolve("Tencent/Shamrock/log").also {
if (it.exists()) it.delete()
it.mkdirs()
}.let {
var i = 1
lateinit var result: File
while (true) {
result = it.resolve("$logFileBaseName$i.log")
if (result.exists()) {
i++
} else {
break
}
}
return@let result
}
.resolve(MobileQQ.getMobileQQ().qqProcessName.replace(":", ".") + "_${
// 格式化时间
SimpleDateFormat("yyyy-MM-dd").format(Date())
}_" + ".log")
private val format = SimpleDateFormat("[HH:mm:ss] ")
fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) =
log({ string }, level, toast)
fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) {
if (!ShamrockConfig.isDebug() && level == Level.DEBUG) {
return
}
if (toast) {
MobileQQ.getContext().toast(string)
}
// 把日志广播到主进程
GlobalScope.launch(Dispatchers.Default) {
DataRequester.request("send_message", bodyBuilder = {
put("string", string)
put("level", level.id)
})
}
if (!LogFile.exists()) {
LogFile.createNewFile()
}
val format = "%s%s %s\n".format(format.format(Date()), level.name, string)
LogFile.appendText(format)
}
fun log(
string: () -> String,

View File

@ -6,6 +6,11 @@ import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.msg.api.IMsgService
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
@ -20,6 +25,8 @@ import moe.fuqiuluo.shamrock.tools.asJsonObjectOrNull
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.json
import moe.fuqiuluo.shamrock.tools.jsonArray
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.math.abs
internal object MessageHelper {
@ -32,35 +39,75 @@ internal object MessageHelper {
): Pair<Long, Int> {
val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, decodeCQCode(message)).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
if (it.second.isEmpty() && !it.first) {
error("消息合成失败,请查看日志或者检查输入。")
} else if (it.second.isEmpty()) {
return System.currentTimeMillis() to 0
}
}.second.filter {
it.elementType != -1
} as ArrayList<MsgElement>
return sendMessageWithoutMsgId(chatType, peerId, msg, callback, fromId)
return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback)
}
@OptIn(DelicateCoroutinesApi::class)
suspend fun sendMessageWithoutMsgId(
chatType: Int,
peerId: String,
message: JsonArray,
callback: IOperateCallback,
fromId: String = peerId
): Pair<Long, Int> {
fromId: String = peerId,
callback: IOperateCallback
): Result<Pair<Long, Int>> {
val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second.filter {
it.elementType != -1
} as ArrayList<MsgElement>
return sendMessageWithoutMsgId(chatType, peerId, msg, callback, fromId)
// ActionMsg No Care
if (msg.isEmpty()) {
return Result.success(System.currentTimeMillis() to 0)
}
val totalSize = msg.filter {
it.elementType == MsgConstant.KELEMTYPEPIC ||
it.elementType == MsgConstant.KELEMTYPEPTT ||
it.elementType == MsgConstant.KELEMTYPEVIDEO
}.map {
(it.picElement?.fileSize ?: 0) + (it.pttElement?.fileSize
?: 0) + (it.videoElement?.fileSize ?: 0)
}.reduceOrNull { a, b -> a + b } ?: 0
val estimateTime = (totalSize / (300 * 1024)) * 1000 + 2000
lateinit var sendResultPair: Pair<Long, Int>
val sendRet = withTimeoutOrNull<Pair<Int, String>>(estimateTime) {
suspendCancellableCoroutine {
GlobalScope.launch {
sendResultPair = sendMessageWithoutMsgId(
chatType,
peerId,
msg,
fromId
) { code, message ->
callback.onResult(code, message)
it.resume(code to message)
}
}
}
}
if (sendRet?.first != 0) {
return Result.failure(SendMsgException(sendRet?.second ?: "发送消息超时"))
}
return Result.success(sendResultPair)
}
suspend fun sendMessageWithoutMsgId(
chatType: Int,
peerId: String,
message: ArrayList<MsgElement>,
callback: IOperateCallback,
fromId: String = peerId
fromId: String = peerId,
callback: IOperateCallback
): Pair<Long, Int> {
return sendMessageWithoutMsgId(generateContact(chatType, peerId, fromId), message, callback)
}
@ -74,7 +121,7 @@ internal object MessageHelper {
val nonMsg: Boolean = message.isEmpty()
return if (!nonMsg) {
val service = QRoute.api(IMsgService::class.java)
if(callback is MsgSvc.MessageCallback) {
if (callback is MsgSvc.MessageCallback) {
callback.msgHash = uniseq.first
}
@ -107,7 +154,7 @@ internal object MessageHelper {
val nonMsg: Boolean = message.isEmpty()
return if (!nonMsg) {
val service = QRoute.api(IMsgService::class.java)
if(callback is MsgSvc.MessageCallback) {
if (callback is MsgSvc.MessageCallback) {
callback.msgHash = uniseq.first
}
@ -132,7 +179,7 @@ internal object MessageHelper {
val nonMsg: Boolean = message.isEmpty()
return if (!nonMsg) {
val service = QRoute.api(IMsgService::class.java)
if(callback is MsgSvc.MessageCallback) {
if (callback is MsgSvc.MessageCallback) {
callback.msgHash = uniseq.first
}
@ -148,6 +195,32 @@ internal object MessageHelper {
}
}
suspend fun sendMessageNoCb(
chatType: Int,
peerId: String,
message: JsonArray,
fromId: String = peerId
): Pair<Int, Long> {
val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second.filter {
it.elementType != -1
} as ArrayList<MsgElement>
val contact = generateContact(chatType, peerId, fromId)
val nonMsg: Boolean = message.isEmpty()
return if (!nonMsg) {
val service = QRoute.api(IMsgService::class.java)
return suspendCoroutine {
service.sendMsg(contact, uniseq.second, msg) { code, why ->
it.resume(code to uniseq.second)
}
}
} else {
-1 to uniseq.second
}
}
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
val peerId = if (MsgConstant.KCHATTYPEC2C == chatType || MsgConstant.KCHATTYPETEMPC2CFROMGROUP == chatType) {
ContactHelper.getUidByUinAsync(id.toLong())
@ -156,7 +229,7 @@ internal object MessageHelper {
}
fun obtainMessageTypeByDetailType(detailType: String): Int {
return when(detailType) {
return when (detailType) {
"troop", "group" -> MsgConstant.KCHATTYPEGROUP
"private" -> MsgConstant.KCHATTYPEC2C
"less" -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
@ -166,7 +239,7 @@ internal object MessageHelper {
}
fun obtainDetailTypeByMsgType(msgType: Int): String {
return when(msgType) {
return when (msgType) {
MsgConstant.KCHATTYPEGROUP -> "group"
MsgConstant.KCHATTYPEC2C -> "private"
MsgConstant.KCHATTYPEGUILD -> "guild"
@ -180,9 +253,9 @@ internal object MessageHelper {
var hasActionMsg = false
messageList.forEach {
val msg = it.jsonObject
try {
val maker = MessageMaker[msg["type"].asString]
if (maker != null) {
val maker = MessageMaker[msg["type"].asString]
if (maker != null) {
try {
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
maker(chatType, msgId, targetUin, data).onSuccess { msgElem ->
msgList.add(msgElem)
@ -193,16 +266,19 @@ internal object MessageHelper {
hasActionMsg = true
}
}
} catch (e: Throwable) {
LogCenter.log(e.stackTraceToString(), Level.ERROR)
}
} catch (e: Throwable) {
LogCenter.log(e.stackTraceToString(), Level.ERROR)
} else {
LogCenter.log("不支持的消息类型: ${msg["type"].asString}", Level.ERROR)
return false to arrayListOf()
}
}
return hasActionMsg to msgList
}
fun generateMsgIdHash(chatType: Int, msgId: Long): Int {
val key = when (chatType) {
val key = when (chatType) {
MsgConstant.KCHATTYPEGROUP -> "grp$msgId"
MsgConstant.KCHATTYPEC2C -> "c2c$msgId"
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> "tmpgrp$msgId"

View File

@ -8,8 +8,10 @@ import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc
import moe.fuqiuluo.shamrock.tools.GlobalClient
import moe.fuqiuluo.shamrock.tools.asInt
import moe.fuqiuluo.shamrock.tools.asJsonArray
import moe.fuqiuluo.shamrock.tools.asJsonArrayOrNull
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.asStringOrNull
import moe.fuqiuluo.shamrock.utils.MD5
internal object MusicHelper {
@ -53,12 +55,26 @@ internal object MusicHelper {
val trackInfo = data["track_info"].asJsonObject
val mid = trackInfo["mid"].asString
val previewMid = trackInfo["album"].asJsonObject["mid"].asString
val singerMid = trackInfo["singer"].asJsonArrayOrNull?.let {
it[0].asJsonObject["mid"].asStringOrNull
} ?: ""
val name = trackInfo["name"].asString
val title = trackInfo["title"].asString
val singerName = trackInfo["singer"].asJsonArray.first().asJsonObject["name"].asString
val vs = trackInfo["vs"].asJsonArrayOrNull?.let {
it[0].asStringOrNull
} ?: ""
val code = MD5.getMd5Hex("${mid}q;z(&l~sdf2!nK".toByteArray()).substring(0 .. 4).uppercase()
val playUrl = "http://c6.y.qq.com/rsc/fcgi-bin/fcg_pyq_play.fcg?songid=&songmid=$mid&songtype=1&fromtag=50&uin=&code=$code"
val previewUrl = "http://y.gtimg.cn/music/photo_new/T002R180x180M000$previewMid.jpg"
val previewUrl = if (vs.isNotEmpty()) {
"http://y.gtimg.cn/music/photo_new/T062R150x150M000$vs}.jpg"
} else if (previewMid.isNotEmpty()) {
"http://y.gtimg.cn/music/photo_new/T002R150x150M000$previewMid.jpg"
} else if (singerMid.isNotEmpty()){
"http://y.gtimg.cn/music/photo_new/T001R150x150M000$singerMid.jpg"
} else {
""
}
val jumpUrl = "https://i.y.qq.com/v8/playsong.html?platform=11&appshare=android_qq&appversion=10030010&hosteuin=oKnlNenz7i-s7c**&songmid=${mid}&type=0&appsongtype=1&_wv=1&source=qq&ADTAG=qfshare"
ArkMsgSvc.tryShareMusic(
chatType,

View File

@ -64,6 +64,7 @@ internal object HTTPServer {
guildAction()
testAction()
requestRouter()
fav()
if (ShamrockConfig.isDev()) {
qsign()
obtainProtocolData()

View File

@ -23,7 +23,7 @@ internal object ActionManager {
// UserActions
GetProfileCard, GetFriendList, SendLike, GetUid, GetUinByUid, ScanQRCode, SetProfileCard,
GetCookies, GetCSRF, GetCredentials, RestartMe, CleanCache, GetModelShow, SetModelShow,
GetModelShowList, GetOnlineClients, GetStrangerInfo, IsBlackListUin, GetHttpCookies,
GetModelShowList, GetOnlineClients, GetStrangerInfo, IsBlackListUin, GetHttpCookies, GetFriendSystemMsg,
// GroupInfo
GetTroopList, GetTroopInfo, GetTroopList, GetTroopMemberInfo, GetTroopMemberList,
@ -31,28 +31,32 @@ internal object ActionManager {
// GroupActions
ModifyTroopName, LeaveTroop, KickTroopMember, BanTroopMember, SetGroupWholeBan, SetGroupAdmin,
ModifyTroopMemberName, SetGroupUnique, GetTroopHonor, GroupPoke, SetEssenceMessage, DeleteEssenceMessage,
GetGroupSystemMsg, GetProhibitedMemberList,
GetGroupSystemMsg, GetProhibitedMemberList, GetEssenceMessageList, GetGroupNotice, SendGroupNotice, SendGroupSign,
GetGroupRemainAtAllRemain,
// MSG ACTIONS
SendMessage, DeleteMessage, GetMsg, GetForwardMsg, SendGroupForwardMsg, SendGroupMessage, SendPrivateMessage,
ClearMsgs, GetHistoryMsg, GetGroupMsgHistory, SendPrivateForwardMsg,
SendMessage, DeleteMessage, GetMsg, GetForwardMsg, SendPrivateForwardMessage, SendGroupMessage, SendPrivateMessage,
ClearMsgs, GetHistoryMsg, GetGroupMsgHistory, SendGroupForwardMessage,
// RESOURCE ACTION
GetRecord, GetImage, UploadGroupFile, CreateGroupFileFolder, DeleteGroupFolder,
DeleteGroupFile, GetGroupFileSystemInfo, GetGroupRootFiles, GetGroupSubFiles,
GetGroupFileUrl, UploadPrivateFile,
//REQUEST ACTION
// REQUEST ACTION
SetFriendAddRequest, SetGroupAddRequest,
// GUILD
GetGuildServiceProfile,
GetGuildServiceProfile, GetGuildList,
// WEATHER
GetWeatherCityCode, GetWeather,
// FAV
FavAddTextMsg, FavAddImageMsg, FavGetItemContent, FavGetItemList,
// OTHER
GetDeviceBattery, DownloadFile
GetDeviceBattery, DownloadFile, QuickOperation
).forEach {
it.alias.forEach { name ->
actionMap[name] = it
@ -193,8 +197,8 @@ internal class ActionSession {
return params[key].asBoolean
}
fun <T: Boolean?> getBooleanOrDefault(key: String, default: T? = null): T {
return (params[key].asBooleanOrNull as? T) ?: default as T
fun getBooleanOrDefault(key: String, default: Boolean? = null): Boolean {
return params[key].asBooleanOrNull ?: default as Boolean
}
fun getObject(key: String): JsonObject {

View File

@ -0,0 +1,155 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import android.graphics.BitmapFactory
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.proto.ProtoUtils
import moe.fuqiuluo.proto.asUtf8String
import moe.fuqiuluo.qqinterface.servlet.QFavSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.utils.CryptTools
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.utils.FileUtils
internal object FavAddImageMsg: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val uin = session.getLong("user_id")
val nickName = session.getString("nick")
val groupName = session.getStringOrNull("group_name") ?: ""
val groupId = session.getLongOrNull("group_id") ?: 0L
val file = session.getString("file")
return invoke(uin, nickName, file, groupName, groupId, session.echo)
}
suspend operator fun invoke(
uin: Long,
nickName: String,
fileText: String,
groupName: String = "",
groupId: Long = 0L,
echo: JsonElement = EmptyJsonString
): String {
val image = fileText.let {
val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase()
if (md5.length == 32) {
FileUtils.getFile(it)
} else {
FileUtils.parseAndSave(it)
}
}
val options = BitmapFactory.Options()
BitmapFactory.decodeFile(image.absolutePath, options)
lateinit var picUrl: String
lateinit var picId: String
lateinit var itemId: String
lateinit var md5: String
QFavSvc.applyUpImageMsg(uin, nickName,
image = image,
groupName = groupName,
groupId = groupId,
width = options.outWidth,
height = options.outHeight
).onSuccess {
if (it.mHttpCode == 200 && it.mResult == 0) {
val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData))
readPacket.discardExact(6)
val allLength = readPacket.readInt()
val dataLength = readPacket.readInt()
val headLength = allLength - dataLength - 16
readPacket.discardExact(2)
ByteArray(headLength).also {
readPacket.readFully(it, 0, it.size)
}
val data = ByteArray(dataLength).also {
readPacket.readFully(it, 0, it.size)
}
val pb = ProtoUtils.decodeFromByteArray(data)
val resp = pb[2, 20010, 1, 2]
picUrl = resp[1].asUtf8String
picId = resp[11].asUtf8String
md5 = resp[4].asUtf8String
} else {
return logic(it.mErrDesc, echo)
}
}.onFailure {
return error(it.message ?: it.toString(), echo)
}
val sha = CryptTools
.getSHA1("/storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/QQ_Collection/pic/" + md5.uppercase() + "_0")
image.inputStream().use {
var offset = 0L
val block = ByteArray(131072)
var rest = image.length()
do {
val length = if (rest <= 131072) rest else 131072L
if(it.read(block, 0, length.toInt()) != -1) {
QFavSvc.sendPicUpBlock(
fileSize = image.length(),
offset = offset,
block = block,
blockSize = length,
pid = picId,
sha = sha
).onFailure {
return error(it.message ?: it.toString(), echo)
}
offset += length
rest -= length
} else {
rest = -1
}
} while (rest > 0)
}
QFavSvc.addImageMsg(
uin, nickName, groupId, groupName, picUrl, picId, options.outWidth, options.outHeight, image.length(), md5.uppercase()
).onFailure {
return error(it.message ?: it.toString(), echo)
}.onSuccess {
if (it.mHttpCode == 200 && it.mResult == 0) {
val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData))
readPacket.discardExact(6)
val allLength = readPacket.readInt()
val dataLength = readPacket.readInt()
val headLength = allLength - dataLength - 16
readPacket.discardExact(2)
ByteArray(headLength).also {
readPacket.readFully(it, 0, it.size)
}
val data = ByteArray(dataLength).also {
readPacket.readFully(it, 0, it.size)
}
val pb = ProtoUtils.decodeFromByteArray(data)
itemId = pb[2, 20009, 1].asUtf8String
}
System.gc()
}
return ok(PicInfo(
picUrl = picUrl,
picId = picId,
id = itemId
), echo)
}
override fun path(): String = "fav.add_image_msg"
override val requiredParams: Array<String> = arrayOf("user_id", "nick", "file")
@Serializable
private data class PicInfo(
@SerialName("pic_url") val picUrl: String,
@SerialName("pic_id") val picId: String,
@SerialName("id") val id: String
)
}

View File

@ -0,0 +1,77 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.proto.ProtoUtils
import moe.fuqiuluo.proto.asUtf8String
import moe.fuqiuluo.qqinterface.servlet.QFavSvc
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
internal object FavAddTextMsg: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val uin = session.getLong("user_id")
val nickName = session.getString("nick")
val groupName = session.getStringOrNull("group_name") ?: ""
val groupId = session.getLongOrNull("group_id") ?: 0L
val time = session.getLongOrNull("time") ?: System.currentTimeMillis()
val content = session.getString("content")
return invoke(uin, nickName, time, content, groupName, groupId, session.echo)
}
suspend operator fun invoke(
uin: Long,
nickName: String,
time: Long = System.currentTimeMillis(),
content: String,
groupName: String = "",
groupId: Long = 0L,
echo: JsonElement = EmptyJsonString
): String {
QFavSvc.addRichMediaMsg(uin, nickName,
time = time,
content = content,
groupName = groupName,
groupId = groupId
).onSuccess {
return if (it.mHttpCode == 200 && it.mResult == 0) {
val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData))
readPacket.discardExact(6)
val allLength = readPacket.readInt()
val dataLength = readPacket.readInt()
val headLength = allLength - dataLength - 16
readPacket.discardExact(2)
ByteArray(headLength).also {
readPacket.readFully(it, 0, it.size)
}
val data = ByteArray(dataLength).also {
readPacket.readFully(it, 0, it.size)
}
val pb = ProtoUtils.decodeFromByteArray(data)
ok(data = QFavItem(
pb[2, 20009, 1].asUtf8String
), echo)
} else {
logic(it.mErrDesc, echo)
}
}
return ok("请求已提交", echo)
}
override fun path(): String = "fav.add_text_msg"
override val requiredParams: Array<String> = arrayOf("user_id", "nick", "content")
@Serializable
private data class QFavItem(
@SerialName("id") val id: String
)
}

View File

@ -0,0 +1,57 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.proto.ProtoUtils
import moe.fuqiuluo.proto.asUtf8String
import moe.fuqiuluo.qqinterface.servlet.QFavSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
internal object FavGetItemContent: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val id = session.getString("id")
return invoke(id, session.echo)
}
suspend operator fun invoke(
id: String,
echo: JsonElement = EmptyJsonString
): String {
val respData = DeflateTools.ungzip(QFavSvc.getItemContent(id).onSuccess {
if (it.mHttpCode != 200 || it.mResult != 0) {
return logic(it.mErrDesc, echo)
}
}.getOrThrow().mRespData)
val readPacket = ByteReadPacket(respData)
readPacket.discardExact(6)
val allLength = readPacket.readInt()
val dataLength = readPacket.readInt()
val headLength = allLength - dataLength - 16
readPacket.discardExact(2)
ByteArray(headLength).also {
readPacket.readFully(it, 0, it.size)
}
val data = ByteArray(dataLength).also {
readPacket.readFully(it, 0, it.size)
}
val pb = ProtoUtils.decodeFromByteArray(data)
return ok(ItemContent(pb[2, 20001, 1, 8, 2].asUtf8String))
}
override fun path(): String = "fav.get_item_content"
override val requiredParams: Array<String> = arrayOf("id")
@Serializable
private data class ItemContent(
@SerialName("content") val content: String
)
}

View File

@ -0,0 +1,117 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.proto.ProtoUtils
import moe.fuqiuluo.proto.asInt
import moe.fuqiuluo.proto.asList
import moe.fuqiuluo.proto.asLong
import moe.fuqiuluo.proto.asMap
import moe.fuqiuluo.proto.asULong
import moe.fuqiuluo.proto.asUtf8String
import moe.fuqiuluo.qqinterface.servlet.QFavSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
internal object FavGetItemList: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val category = session.getInt("category")
val startPos = session.getInt("start_pos")
val pageSize = session.getInt("page_size")
return invoke(category, startPos, pageSize, session.echo)
}
suspend operator fun invoke(
category: Int,
startPos: Int,
pageSize: Int,
echo: JsonElement = EmptyJsonString
): String {
if (pageSize <= 1) {
return logic("page_size must be greater than 1", echo)
}
val result = DeflateTools.ungzip(QFavSvc.getItemList(
category = category,
startPos = startPos,
pageSize = pageSize
).onSuccess {
if (it.mHttpCode != 200 || it.mResult != 0) {
return logic("fav.get_item_list failed", echo)
}
}.getOrThrow().mRespData)
val readPacket = ByteReadPacket(result)
readPacket.discardExact(6)
val allLength = readPacket.readInt()
val dataLength = readPacket.readInt()
val headLength = allLength - dataLength - 16
readPacket.discardExact(2)
ByteArray(headLength).also {
readPacket.readFully(it, 0, it.size)
}
val data = ByteArray(dataLength).also {
readPacket.readFully(it, 0, it.size)
}
val pb = ProtoUtils.decodeFromByteArray(data)
val itemList = arrayListOf<Item>()
val rawItemList = pb[2, 20000, 1].asList
rawItemList.value.forEach {
val item = it.asMap
val itemId = item[1].asUtf8String
val authorType = item[4, 1].asInt
val author = item[4, 2].asULong
val authorName = item[4, 3].asUtf8String
val groupName: String
val groupId: Long
if (authorType == 2) {
groupName = item[4, 5].asUtf8String
groupId = item[4, 4].asULong
} else {
groupName = ""
groupId = 0L
}
val clientVersion = item[7].asUtf8String
val time = item[9].asLong
itemList.add(Item(
id = itemId,
authorType = authorType,
author = author,
authorName = authorName,
groupName = groupName,
groupId = groupId,
clientVersion = clientVersion,
time = time
))
}
return ok(ItemList(itemList), echo)
}
override val requiredParams: Array<String> = arrayOf("category", "start_pos", "page_size")
override fun path(): String = "fav.get_item_list"
@Serializable
private data class ItemList(
val items: List<Item>
)
@Serializable
private data class Item(
@SerialName("id") val id: String,
@SerialName("author_type") val authorType: Int,
@SerialName("author") val author: Long,
@SerialName("author_name") val authorName: String,
@SerialName("group_name") val groupName: String,
@SerialName("group_id") val groupId: Long,
@SerialName("client_version") val clientVersion: String,
@SerialName("time") val time: Long
)
}

View File

@ -0,0 +1,33 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
internal object GetEssenceMessageList: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val groupId = session.getLong("group_id")
val page = session.getIntOrNull("page") ?: 0
val pageSize = session.getIntOrNull("page_size") ?: 20
return invoke(groupId, page, pageSize, session.echo)
}
suspend operator fun invoke(groupId: Long, page: Int = 0, pageSize: Int = 20, echo: JsonElement = EmptyJsonString): String {
if (page < 0 || pageSize > 50) {
return badParam("参数不正确page_size不得大于50", echo)
}
val essenceMessageList = GroupSvc.getEssenceMessageList(groupId, page, pageSize)
if (essenceMessageList.isSuccess) {
return ok(essenceMessageList.getOrNull(), echo)
}
return logic(essenceMessageList.exceptionOrNull()?.message ?: "", echo)
}
override val alias: Array<String> = arrayOf("get_essence_message_list")
override fun path(): String = "get_essence_msg_list"
}

View File

@ -37,9 +37,9 @@ internal object GetForwardMsg: IActionHandler() {
realId = msg.msgSeq.toInt(),
sender = MessageSender(
msg.senderUin, msg.sendNickName
.ifBlank { msg.sendMemberName }
.ifBlank { msg.sendRemarkName }
.ifBlank { msg.peerName }, "unknown", 0, msg.senderUid
.ifEmpty { msg.sendMemberName }
.ifEmpty { msg.sendRemarkName }
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid
),
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
it.toJson()

View File

@ -0,0 +1,45 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.FriendSvc
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.remote.service.data.FriendRequest
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
internal object GetFriendSystemMsg : IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
return invoke(echo = session.echo)
}
suspend operator fun invoke(echo: JsonElement = EmptyJsonString): String {
val list = FriendSvc.requestFriendSystemMsgNew(20)
val msgs = list
// 13 是加别人好友
?.filter { it.msg.sub_type.get() != 13 }
?.map {
LogCenter.log(it.toString(), Level.WARN)
FriendRequest(
seq = it.msg_seq.get(),
userId = it.req_uin.get(),
name = it.msg.req_uin_nick.get(),
source = it.msg.msg_source.get(),
subId = it.msg.src_id.get(),
subSrcId = it.msg.sub_src_id.get(),
msg = it.msg.msg_additional.get(),
sourceGroupName = it.msg.group_name.get(),
sourceGroupCode = it.msg.group_code.get(),
flag = "${it.msg_seq.get()};${it.msg.src_id.get()};${it.msg.sub_src_id.get()};${it.req_uin.get()}",
sex = if (it.msg.req_uin_gender.get() == 1) "female" else "male",
age = it.msg.req_uin_age.get(),
msgDetail = it.msg.msg_detail.get(),
status = it.msg.msg_decided.get()
)
} ?: mutableListOf()
return ok(msgs, echo = echo)
}
override fun path(): String = "get_friend_system_msg"
}

View File

@ -0,0 +1,28 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
internal object GetGroupNotice: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val groupId = session.getLong("group_id")
return invoke(groupId, session.echo)
}
suspend operator fun invoke(groupId: Long, echo: JsonElement = EmptyJsonString): String {
val announcements = GroupSvc.getGroupAnnouncements(groupId)
if (announcements.isSuccess) {
return ok(announcements.getOrNull(), echo)
}
return logic(announcements.exceptionOrNull()?.message ?: "", echo)
}
override val alias: Array<String> = arrayOf("get_group_notice")
override fun path(): String = "_get_group_notice"
}

View File

@ -0,0 +1,29 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
internal object GetGroupRemainAtAllRemain: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val groupId = session.getLong("group_id")
return invoke(groupId, session.echo)
}
suspend operator fun invoke(
groupId: Long,
echo: JsonElement = EmptyJsonString
): String {
val result = GroupSvc.getGroupRemainAtAllRemain(groupId)
if (result.isFailure) {
return error(result.exceptionOrNull()?.message ?: "获取群 @全体成员 剩余次数失败", echo, arrayResult = true)
}
return ok(result.getOrThrow(), echo)
}
override val requiredParams: Array<String> = arrayOf("group_id")
override fun path(): String = "get_group_at_all_remain"
}

View File

@ -10,16 +10,17 @@ import moe.fuqiuluo.shamrock.tools.EmptyJsonString
internal object GetGroupSystemMsg: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
return invoke(session.echo)
return invoke(echo = session.echo)
}
suspend operator fun invoke(echo: JsonElement = EmptyJsonString): String {
val list = GroupSvc.requestGroupSystemMsgNew(20, 0, 0)
val list = GroupSvc.requestGroupSystemMsgNew(20)
val riskList = GroupSvc.requestGroupSystemMsgNew(20, 2)
val msgs = GroupSystemMessage(
invited = mutableListOf(),
join = mutableListOf()
)
list?.forEach {
(list + riskList).forEach {
when(it.msg.group_msg_type.get()) {
22, 1 -> {
// join 进群消息
@ -41,8 +42,8 @@ internal object GetGroupSystemMsg: IActionHandler() {
// invite 别人邀请我
msgs.invited += GroupRequest (
msgSeq = it.msg_seq.get(),
invitorUin = null,
invitorNick = null,
invitorUin = it.msg.action_uin.get(),
invitorNick = it.msg.action_uin_nick.get(),
groupId = it.msg.group_code.get(),
groupName = it.msg.group_name.get(),
checked = it.msg.msg_decided.get().isNotBlank(),
@ -60,7 +61,5 @@ internal object GetGroupSystemMsg: IActionHandler() {
return ok(msgs, echo = echo)
}
override val requiredParams: Array<String> = arrayOf("group_id", "folder_id")
override fun path(): String = "get_group_files_by_folder"
override fun path(): String = "get_group_system_msg"
}

View File

@ -0,0 +1,20 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonArray
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
internal object GetGuildList : IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
return invoke(echo = session.echo)
}
operator fun invoke(echo: JsonElement = EmptyJsonString): String {
// TODO: get_guild_list
return ok(EmptyJsonArray, echo, "此功能尚未实现")
}
override fun path(): String = "get_guild_list"
}

View File

@ -4,16 +4,19 @@ import com.tencent.mobileqq.qqguildsdk.api.IGPSService
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
import mqq.app.MobileQQ
internal object GetGuildServiceProfile: IActionHandler() {
internal object GetGuildServiceProfile : IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
TODO("Not yet implemented")
return invoke(echo = session.echo)
}
operator fun invoke(echo: JsonElement = EmptyJsonString): String {
// TODO: get_guild_service_profile
return ok(EmptyJsonObject, echo, "此功能尚未实现")
val service = AppRuntimeFetcher.appRuntime
.getRuntimeService(IGPSService::class.java, "all")
if (!service.isGProSDKInitCompleted) {

View File

@ -85,9 +85,9 @@ internal object GetHistoryMsg: IActionHandler() {
realId = seq,
sender = MessageSender(
msg.senderUin, msg.sendNickName
.ifBlank { msg.sendMemberName }
.ifBlank { msg.sendRemarkName }
.ifBlank { msg.peerName }, "unknown", 0, msg.senderUid
.ifEmpty { msg.sendMemberName }
.ifEmpty { msg.sendRemarkName }
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid
),
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
it.toJson()

View File

@ -30,9 +30,9 @@ internal object GetMsg: IActionHandler() {
realId = seq,
sender = MessageSender(
msg.senderUin, msg.sendNickName
.ifBlank { msg.sendMemberName }
.ifBlank { msg.sendRemarkName }
.ifBlank { msg.peerName }, "unknown", 0, msg.senderUid
.ifEmpty { msg.sendMemberName }
.ifEmpty { msg.sendRemarkName }
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid
),
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
it.toJson()

View File

@ -29,7 +29,7 @@ internal object GetProfileCard: IActionHandler() {
uin = card.uin.toLong(),
name = card.strNick,
mail = card.strShowName ?: card.strEmail ?: "",
remark = card.strReMark.let { if (it.isNullOrBlank()) card.strAutoRemark else it },
remark = card.strReMark.let { if (it.isNullOrEmpty()) card.strAutoRemark else it },
findMethod = card.addSrcName,
displayName = card.strContactName,
maxVoteCnt = card.bAvailVoteCnt,

View File

@ -30,7 +30,7 @@ internal object GetTroopHonor: IActionHandler() {
GroupSvc.parseHonor(member.honorList).forEach {
val honor = nativeDecodeHonor(member.memberuin, it, member.mHonorRichFlag)
if (honor != null) {
honor.nick = member.troopnick.ifBlank { member.friendnick }
honor.nick = member.troopnick.ifEmpty { member.friendnick }
honorInfo.add(honor)
}
}

View File

@ -58,7 +58,9 @@ internal object GetTroopMemberInfo : IActionHandler() {
unfriendly = false,
title = info.mUniqueTitle ?: "",
titleExpireTime = info.mUniqueTitleExpire,
cardChangeable = GroupSvc.isAdmin(groupId)
cardChangeable = GroupSvc.isAdmin(groupId),
age = info.age.toInt(),
shutUpTimestamp = 0L
), echo
)
}

View File

@ -25,7 +25,9 @@ internal object GetTroopMemberList : IActionHandler() {
val memberList = GroupSvc.getGroupMemberList(groupId, refresh).onFailure {
return error(it.message ?: "unknown error", echo, arrayResult = true)
}.getOrThrow()
val prohibitedMemberList = GroupSvc.getProhibitedMemberList(groupId.toLong())
.getOrDefault(arrayListOf())
.associate { it.memberUin to it.shutuptimestap.toLong() }
return ok(arrayListOf<SimpleTroopMemberInfo>().apply {
memberList.forEach { info ->
if (info.memberuin != "0") {
@ -59,7 +61,9 @@ internal object GetTroopMemberList : IActionHandler() {
unfriendly = false,
title = info.mUniqueTitle ?: "",
titleExpireTime = info.mUniqueTitleExpire,
cardChangeable = GroupSvc.isAdmin(groupId)
cardChangeable = GroupSvc.isAdmin(groupId),
age = 0,
shutUpTimestamp = prohibitedMemberList[info.memberuin.toLong()] ?: 0L
)
)
}

View File

@ -0,0 +1,148 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.remote.service.HttpService
import moe.fuqiuluo.shamrock.tools.asBoolean
import moe.fuqiuluo.shamrock.tools.asBooleanOrNull
import moe.fuqiuluo.shamrock.tools.asInt
import moe.fuqiuluo.shamrock.tools.asIntOrNull
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asLong
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.json
import moe.fuqiuluo.shamrock.tools.jsonArray
internal object QuickOperation: IActionHandler() {
val actionMsgTypes = arrayOf(
"record", "voice", "video", "markdown"
)
override suspend fun internalHandle(session: ActionSession): String {
val botId = session.getLong("self_id")
if (botId != TicketSvc.getLongUin()) {
return logic("当前登录账号和输入的`self_id`不一致", session.echo)
}
val context = session.getObject("context")
//val msgType = context["message_type"].asString
val msgHash = context["message_id"].asInt
//val peerId = context[when(msgType) {
// "group" -> "group_id"
// "private" -> "user_id"
// else -> error("unknown message type: $msgType")
//}].asLong
val record = MsgSvc.getMsg(msgHash).getOrNull()
?: return logic("获取源消息失败", session.echo)
val operation = session.getObject("operation")
if (operation.containsKey("reply")) {
LogCenter.log({ "websocket quickly reply successfully" }, Level.DEBUG)
val autoEscape = operation["auto_escape"].asBooleanOrNull ?: false
val atSender = operation["at_sender"].asBooleanOrNull ?: false
val autoReply = operation["auto_reply"].asBooleanOrNull ?: true
val message = operation["reply"]
if (message is JsonPrimitive) {
if (autoEscape) {
val msgList = mutableSetOf<JsonElement>()
msgList.add(mapOf(
"type" to "text",
"data" to mapOf(
"text" to message.asString
)
).json)
quicklyReply(
record,
msgList.jsonArray,
msgHash,
atSender,
autoReply
)
} else {
val messageArray = MessageHelper.decodeCQCode(message.asString)
quicklyReply(
record,
messageArray,
msgHash,
atSender,
autoReply
)
}
} else if (message is JsonArray) {
quicklyReply(
record,
message,
msgHash,
atSender,
autoReply
)
}
}
if (MsgConstant.KCHATTYPEGROUP == record.chatType && operation.containsKey("delete") && operation["delete"].asBoolean) {
MsgSvc.recallMsg(msgHash)
}
if (MsgConstant.KCHATTYPEGROUP == record.chatType && operation.containsKey("kick") && operation["kick"].asBoolean) {
GroupSvc.kickMember(record.peerUin, false, record.senderUin)
}
if (MsgConstant.KCHATTYPEGROUP == record.chatType && operation.containsKey("ban") && operation["ban"].asBoolean) {
val banTime = operation["ban_duration"].asIntOrNull ?: (30 * 60)
if (banTime <= 0) return logic("禁言时间必须大于0", session.echo)
GroupSvc.banMember(record.peerUin, record.senderUin, banTime)
}
return logic("操作成功", session.echo)
}
override fun path(): String = ".handle_quick_operation_async"
override val requiredParams: Array<String> = arrayOf("context", "operation", "self_id")
suspend fun quicklyReply(
record: MsgRecord,
message: JsonArray,
msgHash: Int,
atSender: Boolean,
autoReply: Boolean
) {
val messageList = mutableListOf<JsonElement>()
message.filter {
it.asJsonObject["type"]?.asString in actionMsgTypes
}.let {
if (it.isNotEmpty()) {
it.map { listOf(it) }.forEach {
MsgSvc.sendToAio(record.chatType, record.peerUin.toString(), it.jsonArray)
}
return
}
}
if (autoReply) messageList.add(mapOf(
"type" to "reply",
"data" to mapOf(
"id" to msgHash
)
).json) // 添加回复
if (MsgConstant.KCHATTYPEGROUP == record.chatType && atSender) {
messageList.add(mapOf(
"type" to "at",
"data" to mapOf(
"qq" to record.senderUin
)
).json) // 添加@发送者
}
messageList.addAll(message)
MsgSvc.sendToAio(record.chatType, record.peerUin.toString(), JsonArray(messageList))
}
}

View File

@ -0,0 +1,176 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo
import kotlinx.serialization.json.*
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.helper.ParamsException
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.remote.service.data.ForwardMessageResult
import moe.fuqiuluo.shamrock.tools.*
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
internal object SendForwardMessage : IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val detailType = session.getStringOrNull("detail_type") ?: session.getStringOrNull("message_type")
try {
val chatType = detailType?.let {
MessageHelper.obtainMessageTypeByDetailType(it)
} ?: run {
if (session.has("user_id")) {
if (session.has("group_id")) {
MsgConstant.KCHATTYPETEMPC2CFROMGROUP
} else {
MsgConstant.KCHATTYPEC2C
}
} else if (session.has("group_id")) {
MsgConstant.KCHATTYPEGROUP
} else {
return noParam("detail_type/message_type", session.echo)
}
}
val peerId = when(chatType) {
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo)
else -> error("unknown chat type: $chatType")
}
val fromId = when(chatType) {
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
MsgConstant.KCHATTYPEC2C -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo)
else -> error("unknown chat type: $chatType")
}
return if (session.isArray("messages")) {
val messages = session.getArray("messages")
invoke(chatType, peerId, messages, fromId, echo = session.echo)
} else {
logic("未知格式合并转发消息", session.echo)
}
} catch (e: ParamsException) {
return noParam(e.message!!, session.echo)
} catch (e: Throwable) {
return logic(e.message ?: e.toString(), session.echo)
}
}
suspend operator fun invoke(
chatType: Int,
peerId: String,
messages: JsonArray,
fromId: String = peerId,
echo: JsonElement = EmptyJsonString
): String {
kotlin.runCatching {
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val msgService = sessionService.msgService
val selfUin = TicketSvc.getUin()
val multiNodes = messages.map {
if (it.asJsonObject["type"].asStringOrNull != "node") {
LogCenter.log("包含非node类型节点", Level.WARN)
return@map null
}
if (it.asJsonObject["data"] !is JsonObject) {
LogCenter.log("data字段错误", Level.WARN)
return@map null
}
it.asJsonObject["data"].asJsonObject.let { data ->
if (data.containsKey("id")) {
val record = MsgSvc.getMsg(data["id"].asInt).getOrNull()
if (record == null) {
LogCenter.log("合并转发消息节点消息获取失败:${data["id"]}", Level.WARN)
return@map null
} else {
record.peerName to record.toSegments().map { segment ->
segment.toJson()
}.json
}
} else if (data.containsKey("content")) {
(data["name"].asStringOrNull ?: "Anno") to when (val raw = data["content"]) {
is JsonObject -> raw.asJsonArray
is JsonArray -> raw.asJsonArray
else -> MessageHelper.decodeCQCode(raw.asString)
}
} else {
LogCenter.log("消息节点缺少id或content字段", Level.WARN)
return@map null
}
}.let { node ->
val content = node.second.map { msg ->
when (msg.asJsonObject["type"].asStringOrNull ?: "text") {
"at" -> {
buildJsonObject {
put("type", "text")
putJsonObject("data") {
put(
"text", "@${
msg.asJsonObject["data"].asJsonObject["name"].asStringOrNull.ifNullOrEmpty(
msg.asJsonObject["data"].asJsonObject["qq"].asString
)
}"
)
}
}
}
"voice" -> {
buildJsonObject {
put("type", "text")
putJsonObject("data") {
put("text", "[语音]")
}
}
}
"node" -> {
LogCenter.log("合并转发消息暂时不支持嵌套", Level.WARN)
buildJsonObject {
put("type", "text")
putJsonObject("data") {
put("text", "[合并转发消息]")
}
}
}
else -> msg
}
}.json
val result = MessageHelper.sendMessageNoCb(MsgConstant.KCHATTYPEC2C, selfUin, content)
if (result.first != 0) {
LogCenter.log("合并转发消息节点消息发送失败", Level.WARN)
}
result.second to node.first
}
}.filterNotNull()
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
val to = MessageHelper.generateContact(chatType, peerId, fromId)
val uniseq = MessageHelper.generateMsgId(chatType)
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply {
multiNodes.forEach { add(MultiMsgInfo(it.first, it.second)) }
}.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.first))
return ok(
ForwardMessageResult(
msgId = uniseq.first,
forwardId = ""
), echo = echo
)
}.onFailure {
return error("error: $it", echo)
}
return logic("合并转发消息失败(unknown error)", echo)
}
override val requiredParams: Array<String> = arrayOf("messages")
override fun path(): String = "send_forward_msg"
}

View File

@ -1,233 +0,0 @@
@file:OptIn(DelicateCoroutinesApi::class)
package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
import moe.fuqiuluo.shamrock.tools.asInt
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.asStringOrNull
import moe.fuqiuluo.shamrock.tools.json
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
/**
* 合并转发消息节点数据类
*/
sealed interface ForwardMsgNode {
class MessageIdNode(
val id: Int
): ForwardMsgNode
open class MessageNode(
val name: String,
val content: JsonElement?
): ForwardMsgNode
object EmptyNode: MessageNode("", null)
}
/**
* 私聊合并转发
*/
internal object SendPrivateForwardMsg: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val groupId = session.getString("user_id")
if (session.isArray("messages")) {
val messages = session.getArray("messages")
return invoke(messages, groupId, session.echo)
}
return logic("未知格式合并转发消息", session.echo)
}
suspend operator fun invoke(
message: JsonArray,
userId: String,
echo: JsonElement = EmptyJsonString
): String {
kotlin.runCatching {
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val msgService = sessionService.msgService
val selfUin = TicketSvc.getUin()
val msgs = message.map {
if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段
it.asJsonObject["data"].asJsonObject.let { data ->
if (data.containsKey("content"))
ForwardMsgNode.MessageNode(
name = data["name"].asStringOrNull ?: "",
content = data["content"]
)
else ForwardMsgNode.MessageIdNode(data["id"].asInt)
}
}.map {
if (it is ForwardMsgNode.MessageIdNode) {
val recordResult = MsgSvc.getMsg(it.id)
if (recordResult.isFailure) {
ForwardMsgNode.EmptyNode
} else {
val record = recordResult.getOrThrow()
ForwardMsgNode.MessageNode(
name = record.sendMemberName
.ifBlank { record.sendNickName }
.ifBlank { record.sendRemarkName }
.ifBlank { record.peerName },
content = record.toSegments().map { segment ->
segment.toJson()
}.json
)
}
} else {
it as ForwardMsgNode.MessageNode
}
}.filter {
it.content != null
}
val multiNodes = msgs.map { node ->
suspendCoroutine {
GlobalScope.launch {
var msgId: Long = 0
msgId = MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, selfUin, node.content!!.let { msg ->
if (msg is JsonArray) msg else MessageHelper.decodeCQCode(msg.asString)
}, { code, why ->
if (code != 0) {
LogCenter.log("合并转发消息节点消息发送失败:$code($why)", Level.WARN)
}
it.resume(node.name to msgId)
}).first
}
}
}
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
val to = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, userId)
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply {
multiNodes.forEach { add(MultiMsgInfo(it.second, it.first)) }
}.also { it.reverse() }, from, to) { code, why ->
if (code != 0)
LogCenter.log("合并转发消息:$code($why)", Level.WARN)
}
return ok(data = EmptyJsonObject, echo = echo)
}.onFailure {
return error("error: $it", echo)
}
return logic("合并转发消息失败(unknown error)", echo)
}
override val requiredParams: Array<String> = arrayOf("user_id")
override fun path(): String = "send_private_forward_msg"
}
/**
* 群聊合并转发
*/
internal object SendGroupForwardMsg: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val groupId = session.getString("group_id")
if (session.isArray("messages")) {
val messages = session.getArray("messages")
return invoke(messages, groupId, session.echo)
}
return logic("未知格式合并转发消息", session.echo)
}
suspend operator fun invoke(
message: JsonArray,
groupId: String,
echo: JsonElement = EmptyJsonString
): String {
kotlin.runCatching {
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val msgService = sessionService.msgService
val selfUin = TicketSvc.getUin()
val msgs = message.map {
if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段
it.asJsonObject["data"].asJsonObject.let { data ->
if (data.containsKey("content"))
ForwardMsgNode.MessageNode(
name = data["name"].asStringOrNull ?: "",
content = data["content"]
)
else ForwardMsgNode.MessageIdNode(data["id"].asInt)
}
}.map {
if (it is ForwardMsgNode.MessageIdNode) {
val recordResult = MsgSvc.getMsg(it.id)
if (recordResult.isFailure) {
ForwardMsgNode.EmptyNode
} else {
val record = recordResult.getOrThrow()
ForwardMsgNode.MessageNode(
name = record.sendMemberName
.ifBlank { record.sendNickName }
.ifBlank { record.sendRemarkName }
.ifBlank { record.peerName },
content = record.toSegments().map { segment ->
segment.toJson()
}.json
)
}
} else {
it as ForwardMsgNode.MessageNode
}
}.filter {
it.content != null
}
val multiNodes = msgs.map { node ->
suspendCoroutine {
GlobalScope.launch {
var msgId: Long = 0
msgId = MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, selfUin, node.content!!.let { msg ->
if (msg is JsonArray) msg else MessageHelper.decodeCQCode(msg.asString)
}, { code, why ->
if (code != 0) {
LogCenter.log("合并转发消息节点消息发送失败:$code($why)", Level.WARN)
}
it.resume(node.name to msgId)
}).first
}
}
}
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
val to = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupId)
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply {
multiNodes.forEach { add(MultiMsgInfo(it.second, it.first)) }
}.also { it.reverse() }, from, to) { code, why ->
if (code != 0)
LogCenter.log("合并转发消息:$code($why)", Level.WARN)
}
return ok(data = EmptyJsonObject, echo = echo)
}.onFailure {
return error("error: $it", echo)
}
return logic("合并转发消息失败(unknown error)", echo)
}
override val requiredParams: Array<String> = arrayOf("group_id")
override fun path(): String = "send_group_forward_msg"
}

View File

@ -0,0 +1,21 @@
package moe.fuqiuluo.shamrock.remote.action.handlers;
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
internal object SendGroupForwardMessage: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val groupId = session.getString("group_id")
return if (session.isArray("messages")) {
val messages = session.getArray("messages")
SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId, messages, echo = session.echo)
} else {
logic("未知格式合并转发消息", session.echo)
}
}
override val requiredParams: Array<String> = arrayOf("messages", "group_id")
override fun path(): String = "send_group_forward_msg"
}

View File

@ -3,17 +3,23 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.tools.jsonArray
internal object SendGroupMessage: IActionHandler() {
override suspend fun internalHandle(session: ActionSession): String {
val groupId = session.getString("group_id")
val retryCnt = session.getIntOrNull("retry_cnt")
val recallDuration = session.getLongOrNull("recall_duration")
return if (session.isString("message")) {
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
val message = session.getString("message")
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, autoEscape, echo = session.echo)
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, autoEscape, echo = session.echo, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
} else if (session.isObject("message")) {
val message = session.getObject("message")
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, listOf( message ).jsonArray, session.echo, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
} else {
val message = session.getArray("message")
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, session.echo)
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, session.echo, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
}
}

Some files were not shown because too many files have changed in this diff Show More