mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
150 Commits
1.0.6
...
b9cfe73eae
Author | SHA1 | Date | |
---|---|---|---|
b9cfe73eae | |||
8d6d984849 | |||
25fe9fab37 | |||
ba7058a838 | |||
0858395e60 | |||
fdd769d9ff | |||
f47ae69653 | |||
f311ae3797 | |||
5a941f889f | |||
69e50c6f93 | |||
f7ac3a5d23 | |||
b6b54a805e | |||
131f56a468 | |||
e45e9e7fa0 | |||
cac0aad1f2 | |||
2c1bd9e726 | |||
2645e8f451 | |||
e92c227bba | |||
a31fe92c0b | |||
70cb876439 | |||
9aa4c37354 | |||
d44150ea1a | |||
0360c81bee | |||
cb904c1f1c | |||
bc754db959 | |||
8df799a6e4 | |||
71dd9469ca | |||
79788d2cdc | |||
c28c9981c4 | |||
8fadd0016a | |||
77504d68fd | |||
1490450178 | |||
88beaf8b6f | |||
df25b0bc76 | |||
d07eea7766 | |||
5000453002 | |||
35c82fcc51 | |||
89a4912ed7 | |||
aeabc66067 | |||
ccbfc9a1e1 | |||
31936feb98 | |||
538db69754 | |||
25ea5bd6a8 | |||
460cd84258 | |||
3f9613c43c | |||
34eccda233 | |||
741d2c7a84 | |||
6ee5ceb321 | |||
6107ec6ffb | |||
4ef014a8ac | |||
135a7c2f56 | |||
420f11784d | |||
951e7462c4 | |||
1b0550b5e1 | |||
919c4a7d80 | |||
dcb2b0a26f | |||
d388e5df0c | |||
5776524579 | |||
1d0a0731fb | |||
4dafa75944 | |||
85cdc86b07 | |||
cd19426d1b | |||
48b1b40e1c | |||
50d469cc45 | |||
37f74d5284 | |||
adb7b12c16 | |||
27791cc848 | |||
bd45523e25 | |||
c0f2242679 | |||
c309a2b3ed | |||
48c9048a00 | |||
4d2f7a794b | |||
ab6e431872 | |||
9423df2670 | |||
a2b3e42eee | |||
d9a045bbf0 | |||
42ca17339e | |||
003c4d4456 | |||
ec39aa7bc3 | |||
4e3870a512 | |||
97534b01a6 | |||
61bed61bfb | |||
a0ff4782db | |||
41dd1de8f8 | |||
fa6634d6af | |||
6201d12f5f | |||
b2adc5cedf | |||
0bb871bf01 | |||
dc969440ee | |||
b9b6e133d0 | |||
b5a9884448 | |||
bffb7caf04 | |||
2c3466b4c3 | |||
007e5fef2f | |||
48773cc47c | |||
b2ad4438ab | |||
2fdcfe332b | |||
7d8772ebf6 | |||
2a75160ef8 | |||
76bd58d984 | |||
39120bdeae | |||
7b07698f7b | |||
5c10a5a04e | |||
3a0dc41329 | |||
64c800c945 | |||
ecb3cea5a5 | |||
8e0ae6f85b | |||
9d893b481d | |||
85aaa54e4e | |||
c6dad5677c | |||
80a4a208b9 | |||
ae663e6b2e | |||
780f3577a5 | |||
3518f974cc | |||
911b003f7f | |||
69bc80e9b3 | |||
da0b74db1a | |||
7212938df3 | |||
ae1e78b267 | |||
b7266e490f | |||
6b4a429821 | |||
4a4507dfcd | |||
f63bcabf1b | |||
4932b36ee1 | |||
8c307c4f6e | |||
8d8846fafb | |||
544e216ddb | |||
4fedab719b | |||
75a567d5cd | |||
1a814e565a | |||
5ea260c24b | |||
2d57dc021d | |||
dabe2ea886 | |||
673902e514 | |||
5062ff7c3a | |||
0de6f851a6 | |||
c758b1576d | |||
5ba8bd11e2 | |||
679b7619ce | |||
282233131a | |||
edf857bcb6 | |||
cd1d1e928a | |||
45d6421153 | |||
8c6f529b4b | |||
b23620b5ef | |||
e09e00fcd3 | |||
0d35d5834b | |||
ee8dc75be3 | |||
5f91be547e | |||
7439622cd6 |
3
.github/ISSUE_TEMPLATE/bug.md
vendored
3
.github/ISSUE_TEMPLATE/bug.md
vendored
@ -6,7 +6,7 @@ labels: bug
|
|||||||
---
|
---
|
||||||
|
|
||||||
警告: 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭
|
警告: 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭
|
||||||
- 确保您使用的是 [最新开发版本](https://github.com/whitechi73/Shamrock/actions/workflows/build-apk.yml) 的 Shamrock.
|
- 确保您使用的是 [最新开发版本](https://github.com/whitechi73/OpenShamrock/actions/workflows/build-apk.yml) 的 Shamrock.
|
||||||
- 确保您的问题尚未在 Issues 列表中提出.
|
- 确保您的问题尚未在 Issues 列表中提出.
|
||||||
- 确保您的问题不是由于您的代码错误导致的.
|
- 确保您的问题不是由于您的代码错误导致的.
|
||||||
|
|
||||||
@ -22,5 +22,6 @@ labels: bug
|
|||||||
|
|
||||||
- Shamrock 版本:
|
- Shamrock 版本:
|
||||||
- Android 版本:
|
- Android 版本:
|
||||||
|
- LSPosed 框架版本:
|
||||||
- 设备的制造商和型号:
|
- 设备的制造商和型号:
|
||||||
- 设备的 CPU 架构:
|
- 设备的 CPU 架构:
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature.md
vendored
2
.github/ISSUE_TEMPLATE/feature.md
vendored
@ -7,7 +7,7 @@ labels: enhancement
|
|||||||
|
|
||||||
警告: 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭
|
警告: 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭
|
||||||
|
|
||||||
- 确保您使用的是 [最新开发版本](https://github.com/whitechi73/Shamrock/actions/workflows/build-apk.yml) 的 Shamrock.
|
- 确保您使用的是 [最新开发版本](https://github.com/whitechi73/OpenShamrock/actions/workflows/build-apk.yml) 的 Shamrock.
|
||||||
- 确保您的功能请求尚未在 Issues 列表中提出.
|
- 确保您的功能请求尚未在 Issues 列表中提出.
|
||||||
- 确保您的功能请求是与 Shamrock 相关的,且可以实现.
|
- 确保您的功能请求是与 Shamrock 相关的,且可以实现.
|
||||||
|
|
||||||
|
65
.github/workflows/build-apk.yml
vendored
65
.github/workflows/build-apk.yml
vendored
@ -13,24 +13,38 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build APK
|
name: Build Shamrock
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-java@v3
|
|
||||||
with:
|
with:
|
||||||
distribution: "temurin"
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup JDK 17
|
||||||
|
uses: actions/setup-java@v4.0.0
|
||||||
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
cache: 'gradle'
|
distribution: "adopt"
|
||||||
|
|
||||||
- name: Setup cmake
|
- name: Cache Gradle Dependencies
|
||||||
run: |
|
uses: actions/cache@v3
|
||||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "cmake;3.22.1" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
with:
|
||||||
echo "sdk.dir=${ANDROID_HOME}" > local.properties
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
!~/.gradle/caches/build-cache-*
|
||||||
|
key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
|
||||||
|
restore-keys: gradle-deps
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Cache Gradle Build
|
||||||
uses: gradle/gradle-build-action@v2.9.0
|
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
|
- name: Build with Gradle
|
||||||
run: |
|
run: |
|
||||||
@ -46,42 +60,41 @@ jobs:
|
|||||||
KEY_ALIAS: ${{ secrets.SIGN_ALIAS }}
|
KEY_ALIAS: ${{ secrets.SIGN_ALIAS }}
|
||||||
KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
|
KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Install aapt
|
|
||||||
run: sudo apt-get update && sudo apt-get install -y aapt
|
|
||||||
|
|
||||||
- name: Set Shamrock Version
|
- name: Set Shamrock Version
|
||||||
run: |
|
run: |
|
||||||
apk_file=${{ env.APK_FILE_ALL }}
|
version_name_all=$(basename -s .apk "${{ env.APK_FILE_ALL }}")
|
||||||
apk_dump=$(aapt dump badging "$apk_file")
|
version_name_arm64=$(basename -s .apk "${{ env.APK_FILE_ARM64 }}")
|
||||||
version_name=$(sed -n "s/.*versionName='\([^']*\)'.*/\1/p" <<< "$apk_dump")
|
version_name_x86_64=$(basename -s .apk "${{ env.APK_FILE_X86_64 }}")
|
||||||
echo "SHAMROCK_VERSION=$version_name" >> $GITHUB_ENV
|
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
|
- name: Show Artifacts SHA256
|
||||||
run: |
|
run: |
|
||||||
echo "### Build Success :rocket:" >> $GITHUB_STEP_SUMMARY
|
echo "### Build Success :rocket:" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "|ABI|SHA256|" >> $GITHUB_STEP_SUMMARY
|
echo "|ABI|SHA256|" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "|:--------:|:----------|" >> $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
|
echo "|all|$all" >> $GITHUB_STEP_SUMMARY
|
||||||
arm64=($(sha256sum ${{ env.APK_FILE_ARM64 }}))
|
arm64=($(sha256sum "${{ env.APK_FILE_ARM64 }}"))
|
||||||
echo "|arm64|$arm64" >> $GITHUB_STEP_SUMMARY
|
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
|
echo "|x86_64|$x86_64" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
- name: Upload ALL APK RELEASE
|
- name: Upload ALL APK RELEASE
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-all
|
name: "${{ env.SHAMROCK_VERSION_ALL }}"
|
||||||
path: ${{ env.APK_FILE_ALL }}
|
path: "${{ env.APK_FILE_ALL }}"
|
||||||
|
|
||||||
- name: Upload ARM64 APK RELEASE
|
- name: Upload ARM64 APK RELEASE
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-arm64
|
name: "${{ env.SHAMROCK_VERSION_ARM64 }}"
|
||||||
path: ${{ env.APK_FILE_ARM64 }}
|
path: "${{ env.APK_FILE_ARM64 }}"
|
||||||
|
|
||||||
- name: Upload X86_64 APK RELEASE
|
- name: Upload X86_64 APK RELEASE
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-x86_64
|
name: "${{ env.SHAMROCK_VERSION_x86_64 }}"
|
||||||
path: ${{ env.APK_FILE_X86_64 }}
|
path: "${{ env.APK_FILE_X86_64 }}"
|
57
.github/workflows/codeql.yml
vendored
57
.github/workflows/codeql.yml
vendored
@ -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}}"
|
|
73
.github/workflows/release.yml
vendored
73
.github/workflows/release.yml
vendored
@ -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 }}
|
|
13
README.md
13
README.md
@ -10,16 +10,17 @@
|
|||||||
![][onebot-12]
|
![][onebot-12]
|
||||||
[![][license]](LICENSE)
|
[![][license]](LICENSE)
|
||||||
|
|
||||||
[下载][download-link] | [部署][deploy-link] | [接口][api-link] | [文档][docs-link] | [加群][group-link]
|
[下载][download-link] | [部署][deploy-link] | [接口][api-link] | [文档][docs-link]
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
☘ 基于 Xposed 实现 OneBot 标准的 QQ 机器人框架,原作者[**fuqiuluo**](https://github.com/fuqiuluo)已脱离开发,接下来由白池接手哦!本项目为OpenShamrock,不会有任何收费行为,欢迎大家的加入!
|
☘ 基于 Lsposed(**Non**-Riru) 实现 OneBot 标准的 QQ 机器人框架,原作者[**fuqiuluo**](https://github.com/fuqiuluo)已脱离开发,接下来由白池接手哦!本项目为OpenShamrock,不会有任何收费行为,欢迎大家的加入!
|
||||||
|
|
||||||
> 本项目仅提供学习与交流用途,请在24小时内删除。
|
> 本项目仅提供学习与交流用途,请在24小时内删除。
|
||||||
> 本项目目的是研究 Xposed 和 LSPosed 框架的使用。 Epic 框架开发相关知识。
|
> 本项目目的是研究 Xposed 和 LSPosed 框架的使用。 Epic 框架开发相关知识。
|
||||||
|
> Riru可能导致封禁,请减少使用。
|
||||||
> 如有违反法律,请联系删除。
|
> 如有违反法律,请联系删除。
|
||||||
> 请勿在任何平台宣传,宣扬,转发本项目,请勿恶意修改企业安装包造成相关企业产生损失,如有违背,必将追责到底。
|
> 请勿在任何平台宣传,宣扬,转发本项目,请勿恶意修改企业安装包造成相关企业产生损失,如有违背,必将追责到底。
|
||||||
> 官方论坛,[点我直达](https://forum.libfekit.so/)!
|
> 官方论坛,[点我直达](https://forum.libfekit.so/)!
|
||||||
@ -29,8 +30,6 @@
|
|||||||
- 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。
|
- 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。
|
||||||
- 平行部署:可多平台部署,未来将会支持 Docker 部署的教程。
|
- 平行部署:可多平台部署,未来将会支持 Docker 部署的教程。
|
||||||
|
|
||||||
> 若您追求小而轻便的Bot服务, [Chronocat](https://chronocat.vercel.app/)是您的不二之选。
|
|
||||||
|
|
||||||
## 权限声明
|
## 权限声明
|
||||||
|
|
||||||
> 如出现未在此处声明的权限,请警惕 Shamrock 是否被修改/植入恶意代码
|
> 如出现未在此处声明的权限,请警惕 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/
|
[docs-link]: https://whitechi73.github.io/OpenShamrock/
|
||||||
|
|
||||||
[group-link]: https://whitechi73.github.io/OpenShamrock/group.html
|
[hook-system]: https://github.com/whitechi73/OpenShamrock/blob/master/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/KeepAlive.kt
|
||||||
|
|
||||||
[hook-system]: https://github.com/whitechi73/OpenShamrock/wiki/perm_hook_android
|
|
||||||
|
|
||||||
[voice-support]: https://whitechi73.github.io/OpenShamrock/advanced/voice.html
|
[voice-support]: https://whitechi73.github.io/OpenShamrock/advanced/voice.html
|
||||||
|
|
||||||
|
@ -1,12 +1,5 @@
|
|||||||
import com.android.build.api.dsl.ApplicationExtension
|
import com.android.build.api.dsl.ApplicationExtension
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
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 ""
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
@ -17,19 +10,20 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
namespace = "moe.fuqiuluo.shamrock"
|
namespace = "moe.fuqiuluo.shamrock"
|
||||||
ndkVersion = "25.1.8937393"
|
ndkVersion = "25.1.8937393"
|
||||||
compileSdk = 33
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "moe.fuqiuluo.shamrock"
|
applicationId = "moe.fuqiuluo.shamrock"
|
||||||
minSdk = 24
|
minSdk = 27
|
||||||
targetSdk = 33
|
targetSdk = 34
|
||||||
versionCode = (System.currentTimeMillis() / 1000).toInt()
|
versionCode = getVersionCode()
|
||||||
versionName = "1.0.6-dev" + gitCommitHash()
|
versionName = "1.0.7" + ".r${getGitCommitCount()}." + getVersionName()
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
}
|
}
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
cppFlags += ""
|
cppFlags += ""
|
||||||
@ -50,8 +44,7 @@ android {
|
|||||||
android.applicationVariants.all {
|
android.applicationVariants.all {
|
||||||
outputs.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
|
outputs.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
|
||||||
.forEach {
|
.forEach {
|
||||||
val abi = it.outputFileName.split("-")[1].split(".apk")[0]
|
val abiName = when (val abi = it.outputFileName.split("-")[1].split(".apk")[0]) {
|
||||||
val abiName = when (abi) {
|
|
||||||
"app" -> "all"
|
"app" -> "all"
|
||||||
"x64" -> "x86_64"
|
"x64" -> "x86_64"
|
||||||
else -> abi
|
else -> abi
|
||||||
@ -69,6 +62,7 @@ android {
|
|||||||
println("Full architecture and full compilation.")
|
println("Full architecture and full compilation.")
|
||||||
abiFilters.add("arm64-v8a")
|
abiFilters.add("arm64-v8a")
|
||||||
abiFilters.add("x86_64")
|
abiFilters.add("x86_64")
|
||||||
|
abiFilters.add("x86")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
create("arm64") {
|
create("arm64") {
|
||||||
@ -133,6 +127,11 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configureAppSigningConfigsForRelease(project)
|
configureAppSigningConfigsForRelease(project)
|
||||||
|
packagingOptions {
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configureAppSigningConfigsForRelease(project: Project) {
|
fun configureAppSigningConfigsForRelease(project: Project) {
|
||||||
@ -154,10 +153,39 @@ fun configureAppSigningConfigsForRelease(project: Project) {
|
|||||||
release {
|
release {
|
||||||
signingConfig = signingConfigs.findByName("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 {
|
dependencies {
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
||||||
|
@ -3,10 +3,6 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<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
|
<application
|
||||||
android:name=".app.MyApplication"
|
android:name=".app.MyApplication"
|
||||||
@ -18,6 +14,7 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Shamrock"
|
android:theme="@style/Theme.Shamrock"
|
||||||
android:zygotePreloadName="@string/app_name"
|
android:zygotePreloadName="@string/app_name"
|
||||||
|
android:multiArch="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@ -47,7 +44,7 @@
|
|||||||
android:value="基于 Xposed 实现 OneBot 标准的 QQ 机器人框架" />
|
android:value="基于 Xposed 实现 OneBot 标准的 QQ 机器人框架" />
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="xposedminversion"
|
android:name="xposedminversion"
|
||||||
android:value="23" />
|
android:value="93" />
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="xposedscope"
|
android:name="xposedscope"
|
||||||
android:resource="@array/xposed_scope" />
|
android:resource="@array/xposed_scope" />
|
||||||
|
@ -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) {
|
void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::string, std::string>>& dest) {
|
||||||
std::string cache;
|
std::string cache;
|
||||||
bool is_start = false;
|
bool is_start = false;
|
||||||
std::string key_tmp;
|
std::string key_tmp;
|
||||||
std::unordered_map<std::string, std::string> kv;
|
std::unordered_map<std::string, std::string> kv;
|
||||||
for(int i = 0; i < code.size(); i++) {
|
for(size_t i = 0; i < code.size(); i++) {
|
||||||
auto c = code[i];
|
int utf8_char_len = utf8_next_len(code, i);
|
||||||
if (c == '[') {
|
if(utf8_char_len == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string_view c(&code[i],utf8_char_len);
|
||||||
|
if (c == "[") {
|
||||||
if (is_start) {
|
if (is_start) {
|
||||||
throw illegal_code();
|
throw illegal_code();
|
||||||
} else {
|
} else {
|
||||||
if (!cache.empty()) {
|
if (!cache.empty()) {
|
||||||
std::unordered_map<std::string, std::string> kv;
|
std::unordered_map<std::string, std::string> kv;
|
||||||
|
replace_string(cache, "[", "[");
|
||||||
|
replace_string(cache, "]", "]");
|
||||||
|
replace_string(cache, "&", "&");
|
||||||
kv.emplace("_type", "text");
|
kv.emplace("_type", "text");
|
||||||
kv.emplace("text", cache);
|
kv.emplace("text", cache);
|
||||||
dest.push_back(kv);
|
dest.push_back(kv);
|
||||||
cache.clear();
|
cache.clear();
|
||||||
}
|
}
|
||||||
auto c1 = code[i + 1];
|
std::string_view cq_flag(&code[i],4);
|
||||||
if (c1 == 'C') {
|
if(cq_flag == "[CQ:"){
|
||||||
i++;
|
is_start = true;
|
||||||
auto c2 = code[i + 1];
|
i += 3;
|
||||||
if(c2 == 'Q') {
|
}else{
|
||||||
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 {
|
|
||||||
cache += c;
|
cache += c;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (c == '=') {
|
else if (c == "=") {
|
||||||
if (is_start) {
|
if (is_start) {
|
||||||
if (cache.empty()) {
|
if (cache.empty()) {
|
||||||
throw illegal_code();
|
throw illegal_code();
|
||||||
@ -70,17 +79,17 @@ void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::
|
|||||||
cache += c;
|
cache += c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (c == ',') {
|
else if (c == ",") {
|
||||||
if (is_start) {
|
if (is_start) {
|
||||||
if (kv.count("_type") == 0 && !cache.empty()) {
|
if (kv.count("_type") == 0 && !cache.empty()) {
|
||||||
kv.emplace("_type", cache);
|
kv.emplace("_type", cache);
|
||||||
cache.clear();
|
cache.clear();
|
||||||
} else {
|
} else {
|
||||||
if (!key_tmp.empty()) {
|
if (!key_tmp.empty()) {
|
||||||
replace_string(cache, "&", "&");
|
|
||||||
replace_string(cache, "[", "[");
|
replace_string(cache, "[", "[");
|
||||||
replace_string(cache, "]", "]");
|
replace_string(cache, "]", "]");
|
||||||
replace_string(cache, ",", ",");
|
replace_string(cache, ",", ",");
|
||||||
|
replace_string(cache, "&", "&");
|
||||||
kv.emplace(key_tmp, cache);
|
kv.emplace(key_tmp, cache);
|
||||||
cache.clear();
|
cache.clear();
|
||||||
key_tmp.clear();
|
key_tmp.clear();
|
||||||
@ -90,15 +99,17 @@ void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::
|
|||||||
cache += c;
|
cache += c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (c == ']') {
|
else if (c == "]") {
|
||||||
if (is_start) {
|
if (is_start) {
|
||||||
if (!cache.empty()) {
|
if (!cache.empty()) {
|
||||||
if (!key_tmp.empty()) {
|
if (!key_tmp.empty()) {
|
||||||
replace_string(cache, "&", "&");
|
|
||||||
replace_string(cache, "[", "[");
|
replace_string(cache, "[", "[");
|
||||||
replace_string(cache, "]", "]");
|
replace_string(cache, "]", "]");
|
||||||
replace_string(cache, ",", ",");
|
replace_string(cache, ",", ",");
|
||||||
|
replace_string(cache, "&", "&");
|
||||||
kv.emplace(key_tmp, cache);
|
kv.emplace(key_tmp, cache);
|
||||||
|
} else {
|
||||||
|
kv.emplace("_type", cache);
|
||||||
}
|
}
|
||||||
dest.push_back(kv);
|
dest.push_back(kv);
|
||||||
kv.clear();
|
kv.clear();
|
||||||
@ -112,10 +123,14 @@ void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
cache += c;
|
cache += c;
|
||||||
|
i += (utf8_char_len - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!cache.empty()) {
|
if (!cache.empty()) {
|
||||||
std::unordered_map<std::string, std::string> kv;
|
std::unordered_map<std::string, std::string> kv;
|
||||||
|
replace_string(cache, "[", "[");
|
||||||
|
replace_string(cache, "]", "]");
|
||||||
|
replace_string(cache, "&", "&");
|
||||||
kv.emplace("_type", "text");
|
kv.emplace("_type", "text");
|
||||||
kv.emplace("text", cache);
|
kv.emplace("text", cache);
|
||||||
dest.push_back(kv);
|
dest.push_back(kv);
|
||||||
|
@ -37,6 +37,26 @@ Java_moe_fuqiuluo_shamrock_utils_MD5_genFileMd5Hex(JNIEnv *env, jobject thiz, js
|
|||||||
return env->NewStringUTF(md5Hex.c_str());
|
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"
|
extern "C"
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_moe_fuqiuluo_shamrock_utils_MD5_getMd5Hex(JNIEnv *env, jobject thiz, jbyteArray bytes) {
|
Java_moe_fuqiuluo_shamrock_utils_MD5_getMd5Hex(JNIEnv *env, jobject thiz, jbyteArray bytes) {
|
||||||
|
@ -52,6 +52,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@ -120,6 +121,7 @@ private fun AppMainView() {
|
|||||||
val coreVersion = remember { mutableStateOf(getShamrockVersion(context)) }
|
val coreVersion = remember { mutableStateOf(getShamrockVersion(context)) }
|
||||||
val coreName = remember { mutableStateOf("Xposed") }
|
val coreName = remember { mutableStateOf("Xposed") }
|
||||||
val voiceSwitch = remember { mutableStateOf(false) }
|
val voiceSwitch = remember { mutableStateOf(false) }
|
||||||
|
@Suppress("LocalVariableName") val LocalString = LocalString.init()
|
||||||
|
|
||||||
if (!AppRuntime.isInit) {
|
if (!AppRuntime.isInit) {
|
||||||
AppRuntime.state = remember {
|
AppRuntime.state = remember {
|
||||||
@ -140,23 +142,22 @@ private fun AppMainView() {
|
|||||||
mutableStateOf("2854200454")
|
mutableStateOf("2854200454")
|
||||||
}
|
}
|
||||||
it.nick = remember {
|
it.nick = remember {
|
||||||
mutableStateOf("测试昵称")
|
mutableStateOf(LocalString.testName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AppRuntime.requestCount = remember { mutableIntStateOf(0) }
|
AppRuntime.requestCount = remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
AppRuntime.isInit = false
|
AppRuntime.isInit = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
@Suppress("LocalVariableName") val LocalString = LocalString
|
|
||||||
LaunchedEffect(isFined.value) {
|
LaunchedEffect(isFined.value) {
|
||||||
if (isFined.value) {
|
if (isFined.value) {
|
||||||
AppRuntime.log("日志框架激活成功,开放操作许可。")
|
AppRuntime.log(LocalString.logCentralLoadSuccessfully)
|
||||||
Toast.makeText(ctx, LocalString.frameworkYes, Toast.LENGTH_SHORT).show()
|
Toast.makeText(ctx, LocalString.frameworkYes, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
AppRuntime.log("日志框架处于未激活状态,请检查。")
|
AppRuntime.log(LocalString.logCentralLoadFailed)
|
||||||
Toast.makeText(ctx, LocalString.frameworkNo, Toast.LENGTH_SHORT).show()
|
Toast.makeText(ctx, LocalString.frameworkNo, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,6 +224,16 @@ object ShamrockConfig {
|
|||||||
preferences.edit().putBoolean("debug", v).apply()
|
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 {
|
fun isInjectPacket(ctx: Context): Boolean {
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
val preferences = ctx.getSharedPreferences("config", 0)
|
||||||
return preferences.getBoolean("inject_packet", false)
|
return preferences.getBoolean("inject_packet", false)
|
||||||
@ -239,6 +249,11 @@ object ShamrockConfig {
|
|||||||
return preferences.getBoolean("enable_auto_start", false)
|
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 {
|
fun allowShell(ctx: Context): Boolean {
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
val preferences = ctx.getSharedPreferences("config", 0)
|
||||||
return preferences.getBoolean("shell", false)
|
return preferences.getBoolean("shell", false)
|
||||||
@ -249,6 +264,11 @@ object ShamrockConfig {
|
|||||||
preferences.edit().putBoolean("enable_auto_start", v).apply()
|
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) {
|
fun setShellStatus(ctx: Context, v: Boolean) {
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
val preferences = ctx.getSharedPreferences("config", 0)
|
||||||
preferences.edit().putBoolean("shell", v).apply()
|
preferences.edit().putBoolean("shell", v).apply()
|
||||||
@ -259,19 +279,19 @@ object ShamrockConfig {
|
|||||||
return preferences.getBoolean("enable_self_msg", false)
|
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) {
|
fun setEnableSelfMsg(ctx: Context, v: Boolean) {
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
val preferences = ctx.getSharedPreferences("config", 0)
|
||||||
preferences.edit().putBoolean("enable_self_msg", v).apply()
|
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)
|
val preferences = ctx.getSharedPreferences("config", 0)
|
||||||
return preferences.getBoolean("echo_number", false)
|
preferences.edit().putBoolean("enable_sync_msg_as_sent_msg", v).apply()
|
||||||
}
|
|
||||||
|
|
||||||
fun setEchoNumber(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("echo_number", v).apply()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getConfigMap(ctx: Context): Map<String, Any?> {
|
fun getConfigMap(ctx: Context): Map<String, Any?> {
|
||||||
@ -293,12 +313,15 @@ object ShamrockConfig {
|
|||||||
"ssl_pwd" to preferences.getString("ssl_pwd", ""),
|
"ssl_pwd" to preferences.getString("ssl_pwd", ""),
|
||||||
"inject_packet" to preferences.getBoolean("inject_packet", false),
|
"inject_packet" to preferences.getBoolean("inject_packet", false),
|
||||||
"debug" to preferences.getBoolean("debug", 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", ""),
|
"ssl_private_pwd" to preferences.getString("ssl_private_pwd", ""),
|
||||||
"key_store" to preferences.getString("key_store", ""),
|
"key_store" to preferences.getString("key_store", ""),
|
||||||
"enable_self_msg" to preferences.getBoolean("enable_self_msg", false),
|
"enable_self_msg" to preferences.getBoolean("enable_self_msg", false),
|
||||||
"echo_number" to preferences.getBoolean("echo_number", false),
|
"echo_number" to preferences.getBoolean("echo_number", false),
|
||||||
"shell" to preferences.getBoolean("shell", 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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
|||||||
import moe.fuqiuluo.shamrock.ui.app.Level
|
import moe.fuqiuluo.shamrock.ui.app.Level
|
||||||
import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig
|
import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.ui.theme.GlobalColor
|
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.theme.ThemeColor
|
||||||
import moe.fuqiuluo.shamrock.ui.tools.InputDialog
|
import moe.fuqiuluo.shamrock.ui.tools.InputDialog
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ fun DashboardFragment(
|
|||||||
AccountCard(nick, uin)
|
AccountCard(nick, uin)
|
||||||
InformationCard(ctx)
|
InformationCard(ctx)
|
||||||
APIInfoCard(ctx)
|
APIInfoCard(ctx)
|
||||||
FunctionCard(scope, ctx, "功能设置")
|
FunctionCard(scope, ctx, LocalString.functionSetting)
|
||||||
SSLCard(ctx)
|
SSLCard(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,7 +81,7 @@ private fun SSLCard(ctx: Context) {
|
|||||||
ActionBox(
|
ActionBox(
|
||||||
modifier = Modifier.padding(top = 12.dp),
|
modifier = Modifier.padding(top = 12.dp),
|
||||||
painter = painterResource(id = R.drawable.baseline_security_24),
|
painter = painterResource(id = R.drawable.baseline_security_24),
|
||||||
title = "SSL配置"
|
title = LocalString.sslSetting
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Divider(
|
Divider(
|
||||||
@ -277,9 +278,7 @@ private fun APIInfoCard(
|
|||||||
text = authToken,
|
text = authToken,
|
||||||
hint = "请填写鉴权token",
|
hint = "请填写鉴权token",
|
||||||
error = "输入的参数不合法",
|
error = "输入的参数不合法",
|
||||||
checker = {
|
checker = { true },
|
||||||
it.length in 0 .. 36
|
|
||||||
},
|
|
||||||
confirm = {
|
confirm = {
|
||||||
ShamrockConfig.setToken(ctx, authToken.value)
|
ShamrockConfig.setToken(ctx, authToken.value)
|
||||||
AppRuntime.log("设置鉴权Token为[${authToken.value}]。")
|
AppRuntime.log("设置鉴权Token为[${authToken.value}]。")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package moe.fuqiuluo.shamrock.ui.fragment
|
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.Column
|
||||||
import androidx.compose.foundation.layout.absolutePadding
|
import androidx.compose.foundation.layout.absolutePadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@ -22,6 +22,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import moe.fuqiuluo.shamrock.R
|
import moe.fuqiuluo.shamrock.R
|
||||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
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.app.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.ui.theme.GlobalColor
|
import moe.fuqiuluo.shamrock.ui.theme.GlobalColor
|
||||||
import moe.fuqiuluo.shamrock.ui.theme.LocalString
|
import moe.fuqiuluo.shamrock.ui.theme.LocalString
|
||||||
@ -46,10 +47,11 @@ fun LabFragment() {
|
|||||||
}
|
}
|
||||||
NoticeTextDialog(
|
NoticeTextDialog(
|
||||||
openDialog = showNoticeDialog,
|
openDialog = showNoticeDialog,
|
||||||
title = "温馨提示",
|
title = LocalString.warnTitle,
|
||||||
text = "实验室功能会导致一些奇怪的问题,请谨慎使用!"
|
text = LocalString.labWarning
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val LocalString = LocalString
|
||||||
ActionBox(
|
ActionBox(
|
||||||
modifier = Modifier.padding(top = 12.dp),
|
modifier = Modifier.padding(top = 12.dp),
|
||||||
painter = painterResource(id = R.drawable.baseline_preview_24),
|
painter = painterResource(id = R.drawable.baseline_preview_24),
|
||||||
@ -63,19 +65,19 @@ fun LabFragment() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
Function(
|
Function(
|
||||||
title = "中二病模式",
|
title = LocalString.b2Mode,
|
||||||
desc = "也许会导致奇怪的问题,大抵就是你看不懂罢了。",
|
desc = LocalString.b2ModeDesc,
|
||||||
descColor = it,
|
descColor = it,
|
||||||
isSwitch = ShamrockConfig.is2B(ctx)
|
isSwitch = ShamrockConfig.is2B(ctx)
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.set2B(ctx, it)
|
ShamrockConfig.set2B(ctx, it)
|
||||||
scope.toast(ctx, "重启生效哦!")
|
scope.toast(ctx, LocalString.restartToast)
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
Function(
|
Function(
|
||||||
title = "显示调试日志",
|
title = LocalString.showDebugLog,
|
||||||
desc = "会导致日志刷屏。",
|
desc = LocalString.showDebugLogDesc,
|
||||||
descColor = it,
|
descColor = it,
|
||||||
isSwitch = ShamrockConfig.isDebug(ctx)
|
isSwitch = ShamrockConfig.isDebug(ctx)
|
||||||
) {
|
) {
|
||||||
@ -90,7 +92,92 @@ fun LabFragment() {
|
|||||||
modifier = Modifier.padding(top = 12.dp),
|
modifier = Modifier.padding(top = 12.dp),
|
||||||
painter = painterResource(id = R.drawable.round_logo_dev_24),
|
painter = painterResource(id = R.drawable.round_logo_dev_24),
|
||||||
title = "实验功能"
|
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 {
|
Column {
|
||||||
Divider(
|
Divider(
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
@ -99,20 +186,9 @@ fun LabFragment() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
Function(
|
Function(
|
||||||
title = "自动清理QQ垃圾",
|
title = LocalString.injectPacket,
|
||||||
desc = "也许会导致奇怪的问题(无效)。",
|
desc = LocalString.injectPacketDesc,
|
||||||
descColor = it,
|
descColor = color,
|
||||||
isSwitch = ShamrockConfig.isAutoClean(ctx)
|
|
||||||
) {
|
|
||||||
ShamrockConfig.setAutoClean(ctx, it)
|
|
||||||
ShamrockConfig.pushUpdate(ctx)
|
|
||||||
return@Function false
|
|
||||||
}
|
|
||||||
|
|
||||||
Function(
|
|
||||||
title = "拦截QQ无用收包",
|
|
||||||
desc = "测试阶段,可能导致网络异常或掉线。",
|
|
||||||
descColor = it,
|
|
||||||
isSwitch = ShamrockConfig.isInjectPacket(ctx)
|
isSwitch = ShamrockConfig.isInjectPacket(ctx)
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.setInjectPacket(ctx, it)
|
ShamrockConfig.setInjectPacket(ctx, it)
|
||||||
@ -121,26 +197,31 @@ fun LabFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Function(
|
Function(
|
||||||
title = "自动唤醒QQ",
|
title = LocalString.antiTrace,
|
||||||
desc = "QQ进程死亡时重新打开QQ进程,前提本进程存活。",
|
desc = LocalString.antiTraceDesc,
|
||||||
descColor = it,
|
descColor = color,
|
||||||
isSwitch = ShamrockConfig.enableAutoStart(ctx)
|
isSwitch = ShamrockConfig.isAntiTrace(ctx)
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.setAutoStart(ctx, it)
|
ShamrockConfig.setAntiTrace(ctx, it)
|
||||||
|
ShamrockConfig.pushUpdate(ctx)
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
Function(
|
kotlin.runCatching {
|
||||||
title = "开启Shell接口",
|
ctx.getSharedPreferences("shared_config", Context.MODE_WORLD_READABLE)
|
||||||
desc = "可能导致设备被入侵,请勿随意开启。",
|
}.onSuccess {
|
||||||
descColor = it,
|
Function(
|
||||||
isSwitch = ShamrockConfig.allowShell(ctx)
|
title = "反检测加强",
|
||||||
) {
|
desc = "可能导致某些设备频繁闪退",
|
||||||
ShamrockConfig.setShellStatus(ctx, it)
|
descColor = color,
|
||||||
return@Function true
|
isSwitch = it.getBoolean("super_anti", false)
|
||||||
|
) { v ->
|
||||||
|
it.edit().putBoolean("super_anti", v).apply()
|
||||||
|
scope.toast(ctx, LocalString.restartToast)
|
||||||
|
return@Function true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionBox(
|
ActionBox(
|
||||||
@ -161,7 +242,11 @@ fun LabFragment() {
|
|||||||
descColor = it,
|
descColor = it,
|
||||||
isSwitch = AppRuntime.state.supportVoice.value
|
isSwitch = AppRuntime.state.supportVoice.value
|
||||||
) {
|
) {
|
||||||
|
if(AppRuntime.state.supportVoice.value) {
|
||||||
|
scope.toast(ctx, "关闭请手动删除文件。")
|
||||||
|
} else {
|
||||||
scope.toast(ctx, "请按照Github提示手动操作。")
|
scope.toast(ctx, "请按照Github提示手动操作。")
|
||||||
|
}
|
||||||
return@Function false
|
return@Function false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,6 +275,17 @@ fun LabFragment() {
|
|||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Function(
|
||||||
|
title = "同步消息推送类型异换",
|
||||||
|
desc = "推送来自同号异设备消息,将同步消息作为自发消息推送。",
|
||||||
|
descColor = it,
|
||||||
|
isSwitch = ShamrockConfig.enableSyncMsgAsSentMsg(ctx)
|
||||||
|
) {
|
||||||
|
ShamrockConfig.setEnableSyncMsgAsSentMsg(ctx, it)
|
||||||
|
ShamrockConfig.pushUpdate(ctx)
|
||||||
|
return@Function true
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Function(
|
Function(
|
||||||
title = "使用纯数字ECHO",
|
title = "使用纯数字ECHO",
|
||||||
|
@ -8,6 +8,7 @@ import android.os.Bundle
|
|||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.request.header
|
import io.ktor.client.request.header
|
||||||
|
import io.ktor.client.request.parameter
|
||||||
import io.ktor.client.request.url
|
import io.ktor.client.request.url
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
@ -58,7 +59,8 @@ object DashboardInitializer {
|
|||||||
url("http://127.0.0.1:$servicePort/get_account_info")
|
url("http://127.0.0.1:$servicePort/get_account_info")
|
||||||
val token = ShamrockConfig.getToken(context)
|
val token = ShamrockConfig.getToken(context)
|
||||||
if (token.isNotBlank()) {
|
if (token.isNotBlank()) {
|
||||||
header("Authorization", "Bearer $token")
|
//header("Authorization", "Bearer $token")
|
||||||
|
parameter("access_token", token)
|
||||||
}
|
}
|
||||||
}.let {
|
}.let {
|
||||||
if (it.status == HttpStatusCode.OK) {
|
if (it.status == HttpStatusCode.OK) {
|
||||||
|
@ -6,7 +6,9 @@ package moe.fuqiuluo.shamrock.ui.theme
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import moe.fuqiuluo.shamrock.R
|
import moe.fuqiuluo.shamrock.R
|
||||||
|
|
||||||
private val LocalStringDefault = Default()
|
private val LocalStringDefault = Default()
|
||||||
@ -69,6 +71,24 @@ private open class Chūnibyō: Default() {
|
|||||||
"执明起,至除免于灾祸。\n" +
|
"执明起,至除免于灾祸。\n" +
|
||||||
"元冥浩浩,非凡不可动之。"
|
"元冥浩浩,非凡不可动之。"
|
||||||
labWarning = "寒酥降矣,梅熟日久,莫不可测。"
|
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" +
|
"同时声明本项目仅用于学习与交流,请于24小时内删除。\n" +
|
||||||
"同时开源贡献者均享受免责条例。",
|
"同时开源贡献者均享受免责条例。",
|
||||||
labWarning = "实验室功能,可能会导致出乎意料的BUG!",
|
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(
|
open class VarString(
|
||||||
@ -99,5 +137,43 @@ open class VarString(
|
|||||||
|
|
||||||
var labWarning: String,
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
5
app/src/main/res/drawable/sharp_lock_24.xml
Normal file
5
app/src/main/res/drawable/sharp_lock_24.xml
Normal 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>
|
@ -1,7 +1,7 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
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("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
|
//id("io.realm.kotlin") version "1.11.0" apply false
|
||||||
}
|
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,7 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
3
gradlew
vendored
3
gradlew
vendored
@ -83,7 +83,8 @@ done
|
|||||||
# This is normally unused
|
# This is normally unused
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
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.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
@ -5,10 +5,10 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "moe.fuqiuluo.qqinterface"
|
namespace = "moe.fuqiuluo.qqinterface"
|
||||||
compileSdk = 33
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 27
|
||||||
|
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SharedPreferences.Editor putBoolean(String str, boolean z) {
|
public SharedPreferences.Editor putBoolean(String s, boolean z) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
@ -21,6 +21,10 @@ public abstract class PBField<T> {
|
|||||||
return new PBBytesField(byteStringMicro, false);
|
return new PBBytesField(byteStringMicro, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PBFloatField initFloat(float paramFloat) {
|
||||||
|
return new PBFloatField(paramFloat, false);
|
||||||
|
}
|
||||||
|
|
||||||
public static PBBoolField initBool(boolean z) {
|
public static PBBoolField initBool(boolean z) {
|
||||||
return new PBBoolField(z, false);
|
return new PBBoolField(z, false);
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.tencent.mobileqq.transfile;
|
||||||
|
|
||||||
|
public interface INetEngineListener {
|
||||||
|
void onResp(NetResp netResp);
|
||||||
|
|
||||||
|
void onUpdateProgeress(NetReq netReq, long j2, long j3);
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.tencent.mobileqq.transfile;
|
||||||
|
|
||||||
|
public interface NetFailedListener {
|
||||||
|
void onFailed(NetResp netResp);
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package com.tencent.qqnt.kernel.nativeinterface;
|
||||||
|
|
||||||
|
public class GuildInteractiveNotificationItem {
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package com.tencent.qqnt.kernel.nativeinterface;
|
||||||
|
|
||||||
|
public class GuildNotificationAbstractInfo {
|
||||||
|
}
|
@ -42,6 +42,10 @@ public interface IKernelMsgListener {
|
|||||||
|
|
||||||
void onGroupTransferInfoUpdate(GroupFileListResult groupFileListResult);
|
void onGroupTransferInfoUpdate(GroupFileListResult groupFileListResult);
|
||||||
|
|
||||||
|
void onGuildInteractiveUpdate(GuildInteractiveNotificationItem guildInteractiveNotificationItem);
|
||||||
|
|
||||||
|
void onGuildNotificationAbstractUpdate(GuildNotificationAbstractInfo guildNotificationAbstractInfo);
|
||||||
|
|
||||||
void onHitCsRelatedEmojiResult(DownloadRelateEmojiResultInfo downloadRelateEmojiResultInfo);
|
void onHitCsRelatedEmojiResult(DownloadRelateEmojiResultInfo downloadRelateEmojiResultInfo);
|
||||||
|
|
||||||
void onHitEmojiKeywordResult(HitRelatedEmojiWordsResult hitRelatedEmojiWordsResult);
|
void onHitEmojiKeywordResult(HitRelatedEmojiWordsResult hitRelatedEmojiWordsResult);
|
||||||
|
@ -18,7 +18,6 @@ public final class MarkdownElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MarkdownElement(String str) {
|
public MarkdownElement(String str) {
|
||||||
this.content = "";
|
|
||||||
this.content = str;
|
this.content = str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package mqq.manager;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import oicq.wlogin_sdk.request.Ticket;
|
import oicq.wlogin_sdk.request.Ticket;
|
||||||
|
import oicq.wlogin_sdk.request.WtTicketPromise;
|
||||||
|
|
||||||
public interface TicketManager extends Manager {
|
public interface TicketManager extends Manager {
|
||||||
String getA2(String uin);
|
String getA2(String uin);
|
||||||
@ -15,6 +16,8 @@ public interface TicketManager extends Manager {
|
|||||||
|
|
||||||
String getPskey(String uin, String domain);
|
String getPskey(String uin, String domain);
|
||||||
|
|
||||||
|
Ticket getPskey(String str, long j2, String[] strArr, WtTicketPromise wtTicketPromise);
|
||||||
|
|
||||||
String getPt4Token(String uin, String domain);
|
String getPt4Token(String uin, String domain);
|
||||||
|
|
||||||
String getSkey(String uin); // 假的Skey
|
String getSkey(String uin); // 假的Skey
|
||||||
|
@ -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);
|
||||||
|
}
|
118
qqinterface/src/main/java/oicq/wlogin_sdk/tools/ErrMsg.java
Normal file
118
qqinterface/src/main/java/oicq/wlogin_sdk/tools/ErrMsg.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -8,12 +8,13 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "moe.fuqiuluo.xposed"
|
namespace = "moe.fuqiuluo.xposed"
|
||||||
compileSdk = 33
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 27
|
||||||
|
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
cppFlags += ""
|
cppFlags += ""
|
||||||
@ -96,5 +97,11 @@ dependencies {
|
|||||||
//ksp("androidx.room:room-compiler:$roomVersion")
|
//ksp("androidx.room:room-compiler:$roomVersion")
|
||||||
// optional - Kotlin Extensions and Coroutines support for Room
|
// optional - Kotlin Extensions and Coroutines support for Room
|
||||||
implementation("androidx.room:room-ktx:$roomVersion")
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
xposed/src/main/assets/native_init
Normal file
1
xposed/src/main/assets/native_init
Normal file
@ -0,0 +1 @@
|
|||||||
|
libclover.so
|
@ -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)
|
cmake_minimum_required(VERSION 3.22.1)
|
||||||
|
|
||||||
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
# Since this is the top level CMakeLists.txt, the project name is also accessible
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
|
|
||||||
# build script scope).
|
project("clover")
|
||||||
project("xposed")
|
|
||||||
|
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
|
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
anti_detection/anti_detection.cpp
|
||||||
xposed.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}
|
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||||
# List libraries link to the target library
|
|
||||||
android
|
android
|
||||||
log)
|
log)
|
||||||
|
6
xposed/src/main/cpp/anti_detection/anti_detection.cpp
Normal file
6
xposed/src/main/cpp/anti_detection/anti_detection.cpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#include "anti_detection.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
26
xposed/src/main/cpp/anti_detection/anti_detection.h
Normal file
26
xposed/src/main/cpp/anti_detection/anti_detection.h
Normal 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
|
189
xposed/src/main/cpp/clover.cpp
Normal file
189
xposed/src/main/cpp/clover.cpp
Normal 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;
|
||||||
|
}
|
26
xposed/src/main/cpp/helper/jnihelper.cpp
Normal file
26
xposed/src/main/cpp/helper/jnihelper.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
|
14
xposed/src/main/cpp/helper/jnihelper.h
Normal file
14
xposed/src/main/cpp/helper/jnihelper.h
Normal 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
|
27
xposed/src/main/cpp/helper/lsposed.h
Normal file
27
xposed/src/main/cpp/helper/lsposed.h
Normal 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
|
@ -1,5 +0,0 @@
|
|||||||
#include <jni.h>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <sys/auxv.h>
|
|
||||||
|
|
@ -50,7 +50,7 @@ val ProtoValue.asLong: Long
|
|||||||
get() = (this as ProtoNumber).value.toLong()
|
get() = (this as ProtoNumber).value.toLong()
|
||||||
|
|
||||||
val ProtoValue.asULong: Long
|
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
|
val ProtoValue.asMap: ProtoMap
|
||||||
get() = (this as ProtoMap)
|
get() = (this as ProtoMap)
|
||||||
|
@ -61,7 +61,7 @@ class ProtoMap(
|
|||||||
var curMap = value
|
var curMap = value
|
||||||
tags.forEachIndexed { index, tag ->
|
tags.forEachIndexed { index, tag ->
|
||||||
if (index == tags.size - 1) {
|
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 ->
|
curMap[tag]?.let { v ->
|
||||||
if (v is ProtoMap) {
|
if (v is ProtoMap) {
|
||||||
@ -69,7 +69,7 @@ class ProtoMap(
|
|||||||
} else {
|
} else {
|
||||||
return v
|
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()})")
|
error("Instance is not ProtoMap for get(${tags.first()})")
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ internal object CardSvc: BaseSvc() {
|
|||||||
val dataService = app
|
val dataService = app
|
||||||
.getRuntimeService(IProfileDataService::class.java, "all")
|
.getRuntimeService(IProfileDataService::class.java, "all")
|
||||||
val card = refreshCardLock.withLock {
|
val card = refreshCardLock.withLock {
|
||||||
suspendCancellableCoroutine<Card?> {
|
suspendCancellableCoroutine {
|
||||||
app.addObserver(object: ProfileCardObserver() {
|
app.addObserver(object: ProfileCardObserver() {
|
||||||
override fun onGetProfileCard(success: Boolean, obj: Any) {
|
override fun onGetProfileCard(success: Boolean, obj: Any) {
|
||||||
app.removeObserver(this)
|
app.removeObserver(this)
|
||||||
|
@ -166,7 +166,11 @@ internal object FileSvc: BaseSvc() {
|
|||||||
modifyTime = fileInfo.uint32_modify_time.get(),
|
modifyTime = fileInfo.uint32_modify_time.get(),
|
||||||
downloadTimes = fileInfo.uint32_download_times.get(),
|
downloadTimes = fileInfo.uint32_download_times.get(),
|
||||||
uploadUin = fileInfo.uint64_uploader_uin.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) {
|
else if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FOLDER) {
|
||||||
|
@ -13,11 +13,9 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
|
||||||
import moe.fuqiuluo.shamrock.tools.slice
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||||
import mqq.app.AppRuntime
|
import mqq.app.AppRuntime
|
||||||
import mqq.app.MobileQQ
|
|
||||||
import tencent.mobileim.structmsg.`structmsg$FlagInfo`
|
import tencent.mobileim.structmsg.`structmsg$FlagInfo`
|
||||||
import tencent.mobileim.structmsg.`structmsg$ReqSystemMsgNew`
|
import tencent.mobileim.structmsg.`structmsg$ReqSystemMsgNew`
|
||||||
import tencent.mobileim.structmsg.`structmsg$RspSystemMsgNew`
|
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`()
|
val req = `structmsg$ReqSystemMsgNew`()
|
||||||
req.msg_num.set(msgNum)
|
req.msg_num.set(msgNum)
|
||||||
req.latest_friend_seq.set(latestFriendSeq)
|
req.latest_friend_seq.set(latestFriendSeq)
|
||||||
@ -90,10 +91,18 @@ internal object FriendSvc: BaseSvc() {
|
|||||||
req.uint32_req_msg_type.set(1)
|
req.uint32_req_msg_type.set(1)
|
||||||
req.uint32_need_uid.set(1)
|
req.uint32_need_uid.set(1)
|
||||||
val respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Friend", true, req.toByteArray())
|
val respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Friend", true, req.toByteArray())
|
||||||
?: return ArrayList()
|
return if (respBuffer == null) {
|
||||||
val msg = `structmsg$RspSystemMsgNew`()
|
ArrayList()
|
||||||
msg.mergeFrom(respBuffer.slice(4))
|
} else {
|
||||||
return msg.friendmsgs.get()
|
try {
|
||||||
|
val msg = `structmsg$RspSystemMsgNew`()
|
||||||
|
msg.mergeFrom(respBuffer.slice(4))
|
||||||
|
return msg.friendmsgs.get()
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
requestFriendSystemMsgNew(msgNum, latestFriendSeq, latestGroupSeq, retryCnt - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package moe.fuqiuluo.qqinterface.servlet
|
package moe.fuqiuluo.qqinterface.servlet
|
||||||
|
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
import com.tencent.common.app.AppInterface
|
import com.tencent.common.app.AppInterface
|
||||||
import com.tencent.mobileqq.app.BusinessHandlerFactory
|
import com.tencent.mobileqq.app.BusinessHandlerFactory
|
||||||
import com.tencent.mobileqq.app.QQAppInterface
|
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.protofile.join_group_link.join_group_link
|
||||||
import com.tencent.qphone.base.remote.ToServiceMsg
|
import com.tencent.qphone.base.remote.ToServiceMsg
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MemberInfo
|
import com.tencent.qqnt.kernel.nativeinterface.MemberInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
import friendlist.stUinInfo
|
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.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -22,22 +38,51 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
|||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
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.ProtoUtils
|
||||||
import moe.fuqiuluo.proto.asInt
|
import moe.fuqiuluo.proto.asInt
|
||||||
import moe.fuqiuluo.proto.asUtf8String
|
import moe.fuqiuluo.proto.asUtf8String
|
||||||
import moe.fuqiuluo.proto.protobufOf
|
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.qqinterface.servlet.entries.ProhibitedMemberInfo
|
||||||
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
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.ifNullOrEmpty
|
||||||
import moe.fuqiuluo.shamrock.tools.putBuf32Long
|
import moe.fuqiuluo.shamrock.tools.putBuf32Long
|
||||||
import moe.fuqiuluo.shamrock.tools.slice
|
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.AppRuntimeFetcher
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
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.cmd0x899.oidb_0x899
|
||||||
import tencent.im.oidb.cmd0x89a.oidb_0x89a
|
import tencent.im.oidb.cmd0x89a.oidb_0x89a
|
||||||
import tencent.im.oidb.cmd0x8a0.oidb_0x8a0
|
import tencent.im.oidb.cmd0x8a0.oidb_0x8a0
|
||||||
|
import tencent.im.oidb.cmd0x8a7.cmd0x8a7
|
||||||
import tencent.im.oidb.cmd0x8fc.Oidb_0x8fc
|
import tencent.im.oidb.cmd0x8fc.Oidb_0x8fc
|
||||||
|
import tencent.im.oidb.cmd0xeb7.oidb_0xeb7
|
||||||
import tencent.im.oidb.oidb_sso
|
import tencent.im.oidb.oidb_sso
|
||||||
|
import tencent.im.troop.honor.troop_honor
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.nio.ByteBuffer
|
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_TROOP_MEM_LIST: Method
|
||||||
private lateinit var METHOD_REQ_MODIFY_GROUP_NAME: 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>> {
|
suspend fun getProhibitedMemberList(groupId: Long): Result<List<ProhibitedMemberInfo>> {
|
||||||
val buffer = sendOidbAW("OidbSvc.0x899_0", 2201, 0, oidb_0x899.ReqBody().apply {
|
val buffer = sendOidbAW("OidbSvc.0x899_0", 2201, 0, oidb_0x899.ReqBody().apply {
|
||||||
uint64_group_code.set(groupId)
|
uint64_group_code.set(groupId)
|
||||||
@ -284,7 +350,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
|
|
||||||
fun getOwner(groupId: String): Long {
|
fun getOwner(groupId: String): Long {
|
||||||
val groupInfo = getGroupInfo(groupId)
|
val groupInfo = getGroupInfo(groupId)
|
||||||
return groupInfo.troopowneruin.toLong()
|
return groupInfo.troopowneruin?.toLong() ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isOwner(groupId: String): Boolean {
|
fun isOwner(groupId: String): Boolean {
|
||||||
@ -386,6 +452,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) {
|
return if (info != null) {
|
||||||
Result.success(info)
|
Result.success(info)
|
||||||
} else {
|
} else {
|
||||||
@ -492,7 +588,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
throw RuntimeException("AppRuntime cannot cast to AppInterface")
|
throw RuntimeException("AppRuntime cannot cast to AppInterface")
|
||||||
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_LIST_HANDLER)
|
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) {
|
if (!GroupSvc::METHOD_REQ_TROOP_MEM_LIST.isInitialized) {
|
||||||
METHOD_REQ_TROOP_MEM_LIST = businessHandler.javaClass.declaredMethods.first {
|
METHOD_REQ_TROOP_MEM_LIST = businessHandler.javaClass.declaredMethods.first {
|
||||||
it.parameterCount == 4
|
it.parameterCount == 4
|
||||||
@ -609,21 +705,6 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
notSee: Boolean? = false,
|
notSee: Boolean? = false,
|
||||||
subType: String
|
subType: String
|
||||||
): Result<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吧
|
// 实在找不到接口了 发pb吧
|
||||||
val buffer: ByteArray
|
val buffer: ByteArray
|
||||||
when (subType) {
|
when (subType) {
|
||||||
@ -654,7 +735,9 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
7 to 1,
|
7 to 1,
|
||||||
8 to mapOf(
|
8 to mapOf(
|
||||||
1 to if (approve != false) 11 else 12,
|
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
|
9 to 1000
|
||||||
).toByteArray()
|
).toByteArray()
|
||||||
@ -670,14 +753,17 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
if (result[1, 1].asInt == 0) {
|
if (result[1, 1].asInt == 0) {
|
||||||
Result.success(result[2].asUtf8String)
|
Result.success(result[2].asUtf8String)
|
||||||
} else {
|
} else {
|
||||||
Result.failure(Exception(result[2].asUtf8String))
|
Result.failure(Exception(result[1, 2].asUtf8String))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Result.failure(Exception("操作失败"))
|
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()
|
val req = ReqSystemMsgNew()
|
||||||
req.msg_num.set(msgNum)
|
req.msg_num.set(msgNum)
|
||||||
req.latest_friend_seq.set(latestFriendSeq)
|
req.latest_friend_seq.set(latestFriendSeq)
|
||||||
@ -706,12 +792,213 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
req.is_get_frd_ribbon.set(false)
|
req.is_get_frd_ribbon.set(false)
|
||||||
req.is_get_grp_ribbon.set(false)
|
req.is_get_grp_ribbon.set(false)
|
||||||
req.friend_msg_type_flag.set(1)
|
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)
|
req.uint32_need_uid.set(1)
|
||||||
val respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Group", true, req.toByteArray())
|
val respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Group", true, req.toByteArray())
|
||||||
?: return ArrayList()
|
return if (respBuffer == null) {
|
||||||
val msg = RspSystemMsgNew()
|
ArrayList()
|
||||||
msg.mergeFrom(respBuffer.slice(4))
|
} else {
|
||||||
return msg.groupmsgs.get()
|
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
|
||||||
|
// 特殊处理 ,目的是替换为换行符,否则会被fromHtml忽略并移除
|
||||||
|
.fromHtml(htmlString.replace(" ", "[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(""", "\"")
|
||||||
|
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(" ")}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,14 +16,13 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.time.withTimeoutOrNull
|
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import moe.fuqiuluo.proto.protobufOf
|
|
||||||
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
|
import moe.fuqiuluo.shamrock.helper.SendMsgException
|
||||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
||||||
@ -174,22 +173,29 @@ internal object MsgSvc: BaseSvc() {
|
|||||||
chatType: Int,
|
chatType: Int,
|
||||||
peedId: String,
|
peedId: String,
|
||||||
message: JsonArray,
|
message: JsonArray,
|
||||||
fromId: String = peedId
|
fromId: String = peedId,
|
||||||
): Pair<Long, Int> {
|
retryCnt: Int = 3
|
||||||
//LogCenter.log(message.toString(), Level.ERROR)
|
): Result<Pair<Long, Int>> {
|
||||||
//callback.msgHash = result.second 什么垃圾代码,万一cb比你快,你不就寄了?
|
|
||||||
|
|
||||||
// 主动临时消息
|
// 主动临时消息
|
||||||
when(chatType) {
|
when (chatType) {
|
||||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||||
prepareTempChatFromGroup(fromId, peedId).onFailure {
|
prepareTempChatFromGroup(fromId, peedId).onFailure {
|
||||||
LogCenter.log("主动临时消息,创建临时会话失败。", Level.ERROR)
|
LogCenter.log("主动临时消息,创建临时会话失败。", Level.ERROR)
|
||||||
return -1L to 0
|
return Result.failure(Exception("主动临时消息,创建临时会话失败。"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val result = MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0))
|
||||||
return MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, MessageCallback(peedId, 0), fromId)
|
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>> {
|
suspend fun getMultiMsg(resId: String): Result<List<MsgRecord>> {
|
||||||
|
436
xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt
Normal file
436
xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -74,8 +74,13 @@ internal object TicketSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCSRF(uin: String, domain: String): String {
|
suspend fun getCSRF(uin: String, domain: String): String {
|
||||||
|
// 是不是要用Skey?
|
||||||
|
return getBkn(getPSKey(uin, domain) ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBkn(arg: String): String {
|
||||||
var v: Long = 5381
|
var v: Long = 5381
|
||||||
for (element in getPSKey(uin, domain) ?: "") {
|
for (element in arg) {
|
||||||
v += (v shl 5 and 2147483647L) + element.code.toLong()
|
v += (v shl 5 and 2147483647L) + element.code.toLong()
|
||||||
}
|
}
|
||||||
return (v and 2147483647L).toString()
|
return (v and 2147483647L).toString()
|
||||||
|
@ -27,6 +27,10 @@ data class FileInfo(
|
|||||||
@SerialName("download_times") val downloadTimes: Int,
|
@SerialName("download_times") val downloadTimes: Int,
|
||||||
@SerialName("uploader") val uploadUin: Long,
|
@SerialName("uploader") val uploadUin: Long,
|
||||||
@SerialName("upload_name") val uploadNick: String,
|
@SerialName("upload_name") val uploadNick: String,
|
||||||
|
@SerialName("sha") val sha: String,
|
||||||
|
@SerialName("sha3") val sha3: String,
|
||||||
|
@SerialName("md5") val md5: String,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -7,4 +7,11 @@ import kotlinx.serialization.Serializable
|
|||||||
internal data class ProhibitedMemberInfo(
|
internal data class ProhibitedMemberInfo(
|
||||||
@SerialName("user_id") val memberUin: Long,
|
@SerialName("user_id") val memberUin: Long,
|
||||||
@SerialName("time") val shutuptimestap: Int
|
@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
|
||||||
)
|
)
|
@ -22,6 +22,9 @@ import com.tencent.qqnt.kernel.nativeinterface.ReplyElement
|
|||||||
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo
|
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.TextElement
|
import com.tencent.qqnt.kernel.nativeinterface.TextElement
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.VideoElement
|
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.JsonObject
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import moe.fuqiuluo.qqinterface.servlet.CardSvc
|
import moe.fuqiuluo.qqinterface.servlet.CardSvc
|
||||||
@ -70,6 +73,8 @@ import tencent.im.oidb.cmd0xdc2.oidb_cmd0xdc2
|
|||||||
import tencent.im.oidb.oidb_sso
|
import tencent.im.oidb.oidb_sso
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.random.nextInt
|
||||||
|
|
||||||
internal typealias IMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
|
internal typealias IMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
|
||||||
|
|
||||||
@ -96,9 +101,91 @@ internal object MessageMaker {
|
|||||||
"touch" to MessageMaker::createTouchElem,
|
"touch" to MessageMaker::createTouchElem,
|
||||||
"weather" to MessageMaker::createWeatherElem,
|
"weather" to MessageMaker::createWeatherElem,
|
||||||
"json" to MessageMaker::createJsonElem,
|
"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,
|
//"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(
|
private suspend fun createJsonElem(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
msgId: Long,
|
msgId: Long,
|
||||||
@ -107,7 +194,20 @@ internal object MessageMaker {
|
|||||||
): Result<MsgElement> {
|
): Result<MsgElement> {
|
||||||
data.checkAndThrow("data")
|
data.checkAndThrow("data")
|
||||||
val jsonStr = data["data"].let {
|
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()
|
val element = MsgElement()
|
||||||
element.elementType = MsgConstant.KELEMTYPEARKSTRUCT
|
element.elementType = MsgConstant.KELEMTYPEARKSTRUCT
|
||||||
@ -174,7 +274,7 @@ internal object MessageMaker {
|
|||||||
element.elementType = MsgConstant.KELEMTYPEREPLY
|
element.elementType = MsgConstant.KELEMTYPEREPLY
|
||||||
val reply = ReplyElement()
|
val reply = ReplyElement()
|
||||||
|
|
||||||
val msgHash = data["id"].asString.toInt()
|
val msgHash = data["id"].asInt
|
||||||
val mapping = MessageHelper.getMsgMappingByHash(msgHash)
|
val mapping = MessageHelper.getMsgMappingByHash(msgHash)
|
||||||
?: return Result.failure(Exception("不存在该消息映射,无法回复消息"))
|
?: return Result.failure(Exception("不存在该消息映射,无法回复消息"))
|
||||||
|
|
||||||
@ -527,10 +627,16 @@ internal object MessageMaker {
|
|||||||
else -> {
|
else -> {
|
||||||
val info = GroupSvc.getTroopMemberInfoByUin(peerId, qq, true).onFailure {
|
val info = GroupSvc.getTroopMemberInfoByUin(peerId, qq, true).onFailure {
|
||||||
LogCenter.log("无法获取群成员信息: $qq", Level.ERROR)
|
LogCenter.log("无法获取群成员信息: $qq", Level.ERROR)
|
||||||
}.getOrThrow()
|
}.getOrNull()
|
||||||
at.content = "@${info.troopnick
|
if (info != null) {
|
||||||
.ifNullOrEmpty(info.friendnick)
|
at.content = "@${
|
||||||
.ifNullOrEmpty(qq)}"
|
info.troopnick
|
||||||
|
.ifNullOrEmpty(info.friendnick)
|
||||||
|
.ifNullOrEmpty(qq)
|
||||||
|
}"
|
||||||
|
} else {
|
||||||
|
at.content = "@${data["name"].asStringOrNull.ifNullOrEmpty(qq)}"
|
||||||
|
}
|
||||||
at.atType = MsgConstant.ATTYPEONE
|
at.atType = MsgConstant.ATTYPEONE
|
||||||
at.atNtUid = ContactHelper.getUidByUinAsync(qq.toLong())
|
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> {
|
private suspend fun createImageElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
|
||||||
data.checkAndThrow("file")
|
|
||||||
val isOriginal = data["original"].asBooleanOrNull ?: true
|
val isOriginal = data["original"].asBooleanOrNull ?: true
|
||||||
val isFlash = data["flash"].asBooleanOrNull ?: false
|
val isFlash = data["flash"].asBooleanOrNull ?: false
|
||||||
val file = data["file"].asString.let {
|
val filePath = data["file"].asStringOrNull
|
||||||
val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase()
|
val url = data["url"].asStringOrNull
|
||||||
var tmpPicFile = if (md5.length == 32) {
|
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)
|
FileUtils.getFile(md5)
|
||||||
} else {
|
} 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.")
|
throw LogicException("Image(${file.name}) file is not exists, please check your filename.")
|
||||||
}
|
}
|
||||||
|
requireNotNull(file)
|
||||||
|
|
||||||
Transfer with when (chatType) {
|
Transfer with when (chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> Troop(peerId)
|
MsgConstant.KCHATTYPEGROUP -> Troop(peerId)
|
||||||
|
@ -53,6 +53,7 @@ internal object MessageConvert {
|
|||||||
MsgConstant.KELEMTYPEREPLY to MessageElemConverter.ReplyConverter,
|
MsgConstant.KELEMTYPEREPLY to MessageElemConverter.ReplyConverter,
|
||||||
MsgConstant.KELEMTYPEGRAYTIP to MessageElemConverter.GrayTipsConverter,
|
MsgConstant.KELEMTYPEGRAYTIP to MessageElemConverter.GrayTipsConverter,
|
||||||
MsgConstant.KELEMTYPEFILE to MessageElemConverter.FileConverter,
|
MsgConstant.KELEMTYPEFILE to MessageElemConverter.FileConverter,
|
||||||
|
MsgConstant.KELEMTYPEMARKDOWN to MessageElemConverter.MarkdownConverter,
|
||||||
//MsgConstant.KELEMTYPEMULTIFORWARD to MessageElemConverter.XmlMultiMsgConverter,
|
//MsgConstant.KELEMTYPEMULTIFORWARD to MessageElemConverter.XmlMultiMsgConverter,
|
||||||
//MsgConstant.KELEMTYPESTRUCTLONGMSG to MessageElemConverter.XmlLongMsgConverter,
|
//MsgConstant.KELEMTYPESTRUCTLONGMSG to MessageElemConverter.XmlLongMsgConverter,
|
||||||
)
|
)
|
||||||
|
@ -55,12 +55,42 @@ internal sealed class MessageElemConverter: IMessageConvert {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return MessageSegment(
|
|
||||||
type = "face",
|
|
||||||
data = hashMapOf(
|
when (face.faceIndex) {
|
||||||
"id" to 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
|
element: MsgElement
|
||||||
): MessageSegment {
|
): MessageSegment {
|
||||||
val tip = element.grayTipElement
|
val tip = element.grayTipElement
|
||||||
when(val tipType = tip.subElementType) {
|
when(tip.subElementType) {
|
||||||
MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> {
|
MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> {
|
||||||
val notify = tip.jsonGrayTipElement
|
val notify = tip.jsonGrayTipElement
|
||||||
when(notify.busiId) {
|
when(notify.busiId) {
|
||||||
/* 新人入群 */ 17L,
|
/* 新人入群 */ 17L, /* 群戳一戳 */1061L,
|
||||||
/* 群戳一戳 */1061L, /* 群撤回 */1014L -> {}
|
/* 群撤回 */1014L, /* 群设精消息 */2401L,
|
||||||
else -> LogCenter.log("不支持的灰条类型(JSON): $tipType", Level.WARN)
|
/* 群头衔 */2407L -> {}
|
||||||
|
else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
|
MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
|
||||||
val notify = tip.xmlElement
|
val notify = tip.xmlElement
|
||||||
when(notify.busiId) {
|
when(notify.busiId) {
|
||||||
/* 群戳一戳 */12L -> {}
|
/* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
|
||||||
else -> LogCenter.log("不支持的灰条类型(XML): $tipType", Level.WARN)
|
else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> LogCenter.log("不支持的提示类型: $tip", Level.WARN)
|
else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN)
|
||||||
}
|
}
|
||||||
// 提示类消息,这里提供的是一个xml,不具备解析通用性
|
// 提示类消息,这里提供的是一个xml,不具备解析通用性
|
||||||
// 在这里不推送
|
// 在这里不推送
|
||||||
@ -327,10 +358,10 @@ internal sealed class MessageElemConverter: IMessageConvert {
|
|||||||
val fileSize = fileMsg.fileSize
|
val fileSize = fileMsg.fileSize
|
||||||
val expireTime = fileMsg.expireTime ?: 0
|
val expireTime = fileMsg.expireTime ?: 0
|
||||||
val fileId = fileMsg.fileUuid
|
val fileId = fileMsg.fileUuid
|
||||||
val bizId = fileMsg.fileBizId
|
val bizId = fileMsg.fileBizId ?: 0
|
||||||
val fileSubId = fileMsg.fileSubId ?: ""
|
val fileSubId = fileMsg.fileSubId ?: ""
|
||||||
val url = if (chatType == MsgConstant.KCHATTYPEC2C) RichProtoSvc.getC2CFileDownUrl(fileId, 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(
|
return MessageSegment(
|
||||||
type = "file",
|
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) {
|
protected fun unknownChatType(chatType: Int) {
|
||||||
throw UnsupportedOperationException("Not supported chat type: $chatType")
|
throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import kotlin.coroutines.resume
|
|||||||
internal object ContactHelper {
|
internal object ContactHelper {
|
||||||
suspend fun getUinByUidAsync(uid: String): String {
|
suspend fun getUinByUidAsync(uid: String): String {
|
||||||
if (uid.isBlank() || uid == "0") {
|
if (uid.isBlank() || uid == "0") {
|
||||||
return "0"
|
return "-1"
|
||||||
}
|
}
|
||||||
|
|
||||||
val kernelService = NTServiceFetcher.kernelService
|
val kernelService = NTServiceFetcher.kernelService
|
||||||
@ -20,7 +20,7 @@ internal object ContactHelper {
|
|||||||
sessionService.uixConvertService.getUin(hashSetOf(uid)) {
|
sessionService.uixConvertService.getUin(hashSetOf(uid)) {
|
||||||
continuation.resume(it)
|
continuation.resume(it)
|
||||||
}
|
}
|
||||||
}[uid]?.toString() ?: "0"
|
}[uid]?.toString() ?: "-1"
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUidByUinAsync(peerId: Long): String {
|
suspend fun getUidByUinAsync(peerId: Long): String {
|
||||||
|
@ -12,3 +12,4 @@ internal class LogicException(why: String) : InternalMessageMakerError(why)
|
|||||||
|
|
||||||
internal object ErrorTokenException : InternalMessageMakerError("access_token error")
|
internal object ErrorTokenException : InternalMessageMakerError("access_token error")
|
||||||
|
|
||||||
|
internal class SendMsgException(why: String) : InternalMessageMakerError(why)
|
||||||
|
@ -13,6 +13,7 @@ import moe.fuqiuluo.shamrock.utils.FileUtils
|
|||||||
import moe.fuqiuluo.shamrock.xposed.actions.toast
|
import moe.fuqiuluo.shamrock.xposed.actions.toast
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester
|
import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
|
import java.io.File
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
internal enum class Level(
|
internal enum class Level(
|
||||||
@ -26,19 +27,53 @@ internal enum class Level(
|
|||||||
|
|
||||||
@SuppressLint("SimpleDateFormat")
|
@SuppressLint("SimpleDateFormat")
|
||||||
internal object LogCenter {
|
internal object LogCenter {
|
||||||
|
private val logFileBaseName = MobileQQ.getMobileQQ().qqProcessName.replace(":", ".") + "_${
|
||||||
|
// 格式化时间
|
||||||
|
SimpleDateFormat("yyyy-MM-dd").format(Date())
|
||||||
|
}_"
|
||||||
private val LogFile = MobileQQ.getContext().getExternalFilesDir(null)!!
|
private val LogFile = MobileQQ.getContext().getExternalFilesDir(null)!!
|
||||||
.parentFile!!.resolve("Tencent/Shamrock/log").also {
|
.parentFile!!.resolve("Tencent/Shamrock/log").also {
|
||||||
if (it.exists()) it.delete()
|
if (it.exists()) it.delete()
|
||||||
it.mkdirs()
|
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] ")
|
private val format = SimpleDateFormat("[HH:mm:ss] ")
|
||||||
|
|
||||||
fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) =
|
fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) {
|
||||||
log({ string }, level, toast)
|
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(
|
fun log(
|
||||||
string: () -> String,
|
string: () -> String,
|
||||||
|
@ -6,6 +6,11 @@ import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback
|
|||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||||
import com.tencent.qqnt.msg.api.IMsgService
|
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.JsonArray
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonObject
|
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.asString
|
||||||
import moe.fuqiuluo.shamrock.tools.json
|
import moe.fuqiuluo.shamrock.tools.json
|
||||||
import moe.fuqiuluo.shamrock.tools.jsonArray
|
import moe.fuqiuluo.shamrock.tools.jsonArray
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
internal object MessageHelper {
|
internal object MessageHelper {
|
||||||
@ -32,35 +39,75 @@ internal object MessageHelper {
|
|||||||
): Pair<Long, Int> {
|
): Pair<Long, Int> {
|
||||||
val uniseq = generateMsgId(chatType)
|
val uniseq = generateMsgId(chatType)
|
||||||
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, decodeCQCode(message)).also {
|
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 {
|
}.second.filter {
|
||||||
it.elementType != -1
|
it.elementType != -1
|
||||||
} as ArrayList<MsgElement>
|
} as ArrayList<MsgElement>
|
||||||
return sendMessageWithoutMsgId(chatType, peerId, msg, callback, fromId)
|
return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
suspend fun sendMessageWithoutMsgId(
|
suspend fun sendMessageWithoutMsgId(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
peerId: String,
|
peerId: String,
|
||||||
message: JsonArray,
|
message: JsonArray,
|
||||||
callback: IOperateCallback,
|
fromId: String = peerId,
|
||||||
fromId: String = peerId
|
callback: IOperateCallback
|
||||||
): Pair<Long, Int> {
|
): Result<Pair<Long, Int>> {
|
||||||
val uniseq = generateMsgId(chatType)
|
val uniseq = generateMsgId(chatType)
|
||||||
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also {
|
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also {
|
||||||
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
|
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
|
||||||
}.second.filter {
|
}.second.filter {
|
||||||
it.elementType != -1
|
it.elementType != -1
|
||||||
} as ArrayList<MsgElement>
|
} 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(
|
suspend fun sendMessageWithoutMsgId(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
peerId: String,
|
peerId: String,
|
||||||
message: ArrayList<MsgElement>,
|
message: ArrayList<MsgElement>,
|
||||||
callback: IOperateCallback,
|
fromId: String = peerId,
|
||||||
fromId: String = peerId
|
callback: IOperateCallback
|
||||||
): Pair<Long, Int> {
|
): Pair<Long, Int> {
|
||||||
return sendMessageWithoutMsgId(generateContact(chatType, peerId, fromId), message, callback)
|
return sendMessageWithoutMsgId(generateContact(chatType, peerId, fromId), message, callback)
|
||||||
}
|
}
|
||||||
@ -74,7 +121,7 @@ internal object MessageHelper {
|
|||||||
val nonMsg: Boolean = message.isEmpty()
|
val nonMsg: Boolean = message.isEmpty()
|
||||||
return if (!nonMsg) {
|
return if (!nonMsg) {
|
||||||
val service = QRoute.api(IMsgService::class.java)
|
val service = QRoute.api(IMsgService::class.java)
|
||||||
if(callback is MsgSvc.MessageCallback) {
|
if (callback is MsgSvc.MessageCallback) {
|
||||||
callback.msgHash = uniseq.first
|
callback.msgHash = uniseq.first
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +154,7 @@ internal object MessageHelper {
|
|||||||
val nonMsg: Boolean = message.isEmpty()
|
val nonMsg: Boolean = message.isEmpty()
|
||||||
return if (!nonMsg) {
|
return if (!nonMsg) {
|
||||||
val service = QRoute.api(IMsgService::class.java)
|
val service = QRoute.api(IMsgService::class.java)
|
||||||
if(callback is MsgSvc.MessageCallback) {
|
if (callback is MsgSvc.MessageCallback) {
|
||||||
callback.msgHash = uniseq.first
|
callback.msgHash = uniseq.first
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +179,7 @@ internal object MessageHelper {
|
|||||||
val nonMsg: Boolean = message.isEmpty()
|
val nonMsg: Boolean = message.isEmpty()
|
||||||
return if (!nonMsg) {
|
return if (!nonMsg) {
|
||||||
val service = QRoute.api(IMsgService::class.java)
|
val service = QRoute.api(IMsgService::class.java)
|
||||||
if(callback is MsgSvc.MessageCallback) {
|
if (callback is MsgSvc.MessageCallback) {
|
||||||
callback.msgHash = uniseq.first
|
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 {
|
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
|
||||||
val peerId = if (MsgConstant.KCHATTYPEC2C == chatType || MsgConstant.KCHATTYPETEMPC2CFROMGROUP == chatType) {
|
val peerId = if (MsgConstant.KCHATTYPEC2C == chatType || MsgConstant.KCHATTYPETEMPC2CFROMGROUP == chatType) {
|
||||||
ContactHelper.getUidByUinAsync(id.toLong())
|
ContactHelper.getUidByUinAsync(id.toLong())
|
||||||
@ -156,7 +229,7 @@ internal object MessageHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun obtainMessageTypeByDetailType(detailType: String): Int {
|
fun obtainMessageTypeByDetailType(detailType: String): Int {
|
||||||
return when(detailType) {
|
return when (detailType) {
|
||||||
"troop", "group" -> MsgConstant.KCHATTYPEGROUP
|
"troop", "group" -> MsgConstant.KCHATTYPEGROUP
|
||||||
"private" -> MsgConstant.KCHATTYPEC2C
|
"private" -> MsgConstant.KCHATTYPEC2C
|
||||||
"less" -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
"less" -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||||
@ -166,7 +239,7 @@ internal object MessageHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun obtainDetailTypeByMsgType(msgType: Int): String {
|
fun obtainDetailTypeByMsgType(msgType: Int): String {
|
||||||
return when(msgType) {
|
return when (msgType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> "group"
|
MsgConstant.KCHATTYPEGROUP -> "group"
|
||||||
MsgConstant.KCHATTYPEC2C -> "private"
|
MsgConstant.KCHATTYPEC2C -> "private"
|
||||||
MsgConstant.KCHATTYPEGUILD -> "guild"
|
MsgConstant.KCHATTYPEGUILD -> "guild"
|
||||||
@ -180,9 +253,9 @@ internal object MessageHelper {
|
|||||||
var hasActionMsg = false
|
var hasActionMsg = false
|
||||||
messageList.forEach {
|
messageList.forEach {
|
||||||
val msg = it.jsonObject
|
val msg = it.jsonObject
|
||||||
try {
|
val maker = MessageMaker[msg["type"].asString]
|
||||||
val maker = MessageMaker[msg["type"].asString]
|
if (maker != null) {
|
||||||
if (maker != null) {
|
try {
|
||||||
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
|
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
|
||||||
maker(chatType, msgId, targetUin, data).onSuccess { msgElem ->
|
maker(chatType, msgId, targetUin, data).onSuccess { msgElem ->
|
||||||
msgList.add(msgElem)
|
msgList.add(msgElem)
|
||||||
@ -193,16 +266,19 @@ internal object MessageHelper {
|
|||||||
hasActionMsg = true
|
hasActionMsg = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
LogCenter.log(e.stackTraceToString(), Level.ERROR)
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} else {
|
||||||
LogCenter.log(e.stackTraceToString(), Level.ERROR)
|
LogCenter.log("不支持的消息类型: ${msg["type"].asString}", Level.ERROR)
|
||||||
|
return false to arrayListOf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasActionMsg to msgList
|
return hasActionMsg to msgList
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generateMsgIdHash(chatType: Int, msgId: Long): Int {
|
fun generateMsgIdHash(chatType: Int, msgId: Long): Int {
|
||||||
val key = when (chatType) {
|
val key = when (chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> "grp$msgId"
|
MsgConstant.KCHATTYPEGROUP -> "grp$msgId"
|
||||||
MsgConstant.KCHATTYPEC2C -> "c2c$msgId"
|
MsgConstant.KCHATTYPEC2C -> "c2c$msgId"
|
||||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> "tmpgrp$msgId"
|
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> "tmpgrp$msgId"
|
||||||
|
@ -8,8 +8,10 @@ import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc
|
|||||||
import moe.fuqiuluo.shamrock.tools.GlobalClient
|
import moe.fuqiuluo.shamrock.tools.GlobalClient
|
||||||
import moe.fuqiuluo.shamrock.tools.asInt
|
import moe.fuqiuluo.shamrock.tools.asInt
|
||||||
import moe.fuqiuluo.shamrock.tools.asJsonArray
|
import moe.fuqiuluo.shamrock.tools.asJsonArray
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asJsonArrayOrNull
|
||||||
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||||
import moe.fuqiuluo.shamrock.tools.asString
|
import moe.fuqiuluo.shamrock.tools.asString
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asStringOrNull
|
||||||
import moe.fuqiuluo.shamrock.utils.MD5
|
import moe.fuqiuluo.shamrock.utils.MD5
|
||||||
|
|
||||||
internal object MusicHelper {
|
internal object MusicHelper {
|
||||||
@ -53,12 +55,26 @@ internal object MusicHelper {
|
|||||||
val trackInfo = data["track_info"].asJsonObject
|
val trackInfo = data["track_info"].asJsonObject
|
||||||
val mid = trackInfo["mid"].asString
|
val mid = trackInfo["mid"].asString
|
||||||
val previewMid = trackInfo["album"].asJsonObject["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 name = trackInfo["name"].asString
|
||||||
val title = trackInfo["title"].asString
|
val title = trackInfo["title"].asString
|
||||||
val singerName = trackInfo["singer"].asJsonArray.first().asJsonObject["name"].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 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 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"
|
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(
|
ArkMsgSvc.tryShareMusic(
|
||||||
chatType,
|
chatType,
|
||||||
|
@ -64,6 +64,7 @@ internal object HTTPServer {
|
|||||||
guildAction()
|
guildAction()
|
||||||
testAction()
|
testAction()
|
||||||
requestRouter()
|
requestRouter()
|
||||||
|
fav()
|
||||||
if (ShamrockConfig.isDev()) {
|
if (ShamrockConfig.isDev()) {
|
||||||
qsign()
|
qsign()
|
||||||
obtainProtocolData()
|
obtainProtocolData()
|
||||||
|
@ -23,7 +23,7 @@ internal object ActionManager {
|
|||||||
// UserActions
|
// UserActions
|
||||||
GetProfileCard, GetFriendList, SendLike, GetUid, GetUinByUid, ScanQRCode, SetProfileCard,
|
GetProfileCard, GetFriendList, SendLike, GetUid, GetUinByUid, ScanQRCode, SetProfileCard,
|
||||||
GetCookies, GetCSRF, GetCredentials, RestartMe, CleanCache, GetModelShow, SetModelShow,
|
GetCookies, GetCSRF, GetCredentials, RestartMe, CleanCache, GetModelShow, SetModelShow,
|
||||||
GetModelShowList, GetOnlineClients, GetStrangerInfo, IsBlackListUin, GetHttpCookies,
|
GetModelShowList, GetOnlineClients, GetStrangerInfo, IsBlackListUin, GetHttpCookies, GetFriendSystemMsg,
|
||||||
|
|
||||||
// GroupInfo
|
// GroupInfo
|
||||||
GetTroopList, GetTroopInfo, GetTroopList, GetTroopMemberInfo, GetTroopMemberList,
|
GetTroopList, GetTroopInfo, GetTroopList, GetTroopMemberInfo, GetTroopMemberList,
|
||||||
@ -31,28 +31,32 @@ internal object ActionManager {
|
|||||||
// GroupActions
|
// GroupActions
|
||||||
ModifyTroopName, LeaveTroop, KickTroopMember, BanTroopMember, SetGroupWholeBan, SetGroupAdmin,
|
ModifyTroopName, LeaveTroop, KickTroopMember, BanTroopMember, SetGroupWholeBan, SetGroupAdmin,
|
||||||
ModifyTroopMemberName, SetGroupUnique, GetTroopHonor, GroupPoke, SetEssenceMessage, DeleteEssenceMessage,
|
ModifyTroopMemberName, SetGroupUnique, GetTroopHonor, GroupPoke, SetEssenceMessage, DeleteEssenceMessage,
|
||||||
GetGroupSystemMsg, GetProhibitedMemberList,
|
GetGroupSystemMsg, GetProhibitedMemberList, GetEssenceMessageList, GetGroupNotice, SendGroupNotice, SendGroupSign,
|
||||||
|
GetGroupRemainAtAllRemain,
|
||||||
|
|
||||||
// MSG ACTIONS
|
// MSG ACTIONS
|
||||||
SendMessage, DeleteMessage, GetMsg, GetForwardMsg, SendGroupForwardMsg, SendGroupMessage, SendPrivateMessage,
|
SendMessage, DeleteMessage, GetMsg, GetForwardMsg, SendPrivateForwardMessage, SendGroupMessage, SendPrivateMessage,
|
||||||
ClearMsgs, GetHistoryMsg, GetGroupMsgHistory, SendPrivateForwardMsg,
|
ClearMsgs, GetHistoryMsg, GetGroupMsgHistory, SendGroupForwardMessage,
|
||||||
|
|
||||||
// RESOURCE ACTION
|
// RESOURCE ACTION
|
||||||
GetRecord, GetImage, UploadGroupFile, CreateGroupFileFolder, DeleteGroupFolder,
|
GetRecord, GetImage, UploadGroupFile, CreateGroupFileFolder, DeleteGroupFolder,
|
||||||
DeleteGroupFile, GetGroupFileSystemInfo, GetGroupRootFiles, GetGroupSubFiles,
|
DeleteGroupFile, GetGroupFileSystemInfo, GetGroupRootFiles, GetGroupSubFiles,
|
||||||
GetGroupFileUrl, UploadPrivateFile,
|
GetGroupFileUrl, UploadPrivateFile,
|
||||||
|
|
||||||
//REQUEST ACTION
|
// REQUEST ACTION
|
||||||
SetFriendAddRequest, SetGroupAddRequest,
|
SetFriendAddRequest, SetGroupAddRequest,
|
||||||
|
|
||||||
// GUILD
|
// GUILD
|
||||||
GetGuildServiceProfile,
|
GetGuildServiceProfile, GetGuildList,
|
||||||
|
|
||||||
// WEATHER
|
// WEATHER
|
||||||
GetWeatherCityCode, GetWeather,
|
GetWeatherCityCode, GetWeather,
|
||||||
|
|
||||||
|
// FAV
|
||||||
|
FavAddTextMsg, FavAddImageMsg, FavGetItemContent, FavGetItemList,
|
||||||
|
|
||||||
// OTHER
|
// OTHER
|
||||||
GetDeviceBattery, DownloadFile
|
GetDeviceBattery, DownloadFile, QuickOperation
|
||||||
).forEach {
|
).forEach {
|
||||||
it.alias.forEach { name ->
|
it.alias.forEach { name ->
|
||||||
actionMap[name] = it
|
actionMap[name] = it
|
||||||
@ -193,8 +197,8 @@ internal class ActionSession {
|
|||||||
return params[key].asBoolean
|
return params[key].asBoolean
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T: Boolean?> getBooleanOrDefault(key: String, default: T? = null): T {
|
fun getBooleanOrDefault(key: String, default: Boolean? = null): Boolean {
|
||||||
return (params[key].asBooleanOrNull as? T) ?: default as T
|
return params[key].asBooleanOrNull ?: default as Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getObject(key: String): JsonObject {
|
fun getObject(key: String): JsonObject {
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
@ -10,16 +10,17 @@ import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
|||||||
|
|
||||||
internal object GetGroupSystemMsg: IActionHandler() {
|
internal object GetGroupSystemMsg: IActionHandler() {
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
return invoke(session.echo)
|
return invoke(echo = session.echo)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend operator fun invoke(echo: JsonElement = EmptyJsonString): String {
|
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(
|
val msgs = GroupSystemMessage(
|
||||||
invited = mutableListOf(),
|
invited = mutableListOf(),
|
||||||
join = mutableListOf()
|
join = mutableListOf()
|
||||||
)
|
)
|
||||||
list?.forEach {
|
(list + riskList).forEach {
|
||||||
when(it.msg.group_msg_type.get()) {
|
when(it.msg.group_msg_type.get()) {
|
||||||
22, 1 -> {
|
22, 1 -> {
|
||||||
// join 进群消息
|
// join 进群消息
|
||||||
@ -41,8 +42,8 @@ internal object GetGroupSystemMsg: IActionHandler() {
|
|||||||
// invite 别人邀请我
|
// invite 别人邀请我
|
||||||
msgs.invited += GroupRequest (
|
msgs.invited += GroupRequest (
|
||||||
msgSeq = it.msg_seq.get(),
|
msgSeq = it.msg_seq.get(),
|
||||||
invitorUin = null,
|
invitorUin = it.msg.action_uin.get(),
|
||||||
invitorNick = null,
|
invitorNick = it.msg.action_uin_nick.get(),
|
||||||
groupId = it.msg.group_code.get(),
|
groupId = it.msg.group_code.get(),
|
||||||
groupName = it.msg.group_name.get(),
|
groupName = it.msg.group_name.get(),
|
||||||
checked = it.msg.msg_decided.get().isNotBlank(),
|
checked = it.msg.msg_decided.get().isNotBlank(),
|
||||||
@ -60,7 +61,5 @@ internal object GetGroupSystemMsg: IActionHandler() {
|
|||||||
return ok(msgs, echo = echo)
|
return ok(msgs, echo = echo)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val requiredParams: Array<String> = arrayOf("group_id", "folder_id")
|
override fun path(): String = "get_group_system_msg"
|
||||||
|
|
||||||
override fun path(): String = "get_group_files_by_folder"
|
|
||||||
}
|
}
|
@ -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"
|
||||||
|
}
|
@ -4,16 +4,19 @@ import com.tencent.mobileqq.qqguildsdk.api.IGPSService
|
|||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||||
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
|
||||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
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 {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
TODO("Not yet implemented")
|
return invoke(echo = session.echo)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun invoke(echo: JsonElement = EmptyJsonString): String {
|
operator fun invoke(echo: JsonElement = EmptyJsonString): String {
|
||||||
|
// TODO: get_guild_service_profile
|
||||||
|
return ok(EmptyJsonObject, echo, "此功能尚未实现")
|
||||||
|
|
||||||
val service = AppRuntimeFetcher.appRuntime
|
val service = AppRuntimeFetcher.appRuntime
|
||||||
.getRuntimeService(IGPSService::class.java, "all")
|
.getRuntimeService(IGPSService::class.java, "all")
|
||||||
if (!service.isGProSDKInitCompleted) {
|
if (!service.isGProSDKInitCompleted) {
|
||||||
|
@ -58,7 +58,9 @@ internal object GetTroopMemberInfo : IActionHandler() {
|
|||||||
unfriendly = false,
|
unfriendly = false,
|
||||||
title = info.mUniqueTitle ?: "",
|
title = info.mUniqueTitle ?: "",
|
||||||
titleExpireTime = info.mUniqueTitleExpire,
|
titleExpireTime = info.mUniqueTitleExpire,
|
||||||
cardChangeable = GroupSvc.isAdmin(groupId)
|
cardChangeable = GroupSvc.isAdmin(groupId),
|
||||||
|
age = info.age.toInt(),
|
||||||
|
shutUpTimestamp = 0L
|
||||||
), echo
|
), echo
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,9 @@ internal object GetTroopMemberList : IActionHandler() {
|
|||||||
val memberList = GroupSvc.getGroupMemberList(groupId, refresh).onFailure {
|
val memberList = GroupSvc.getGroupMemberList(groupId, refresh).onFailure {
|
||||||
return error(it.message ?: "unknown error", echo, arrayResult = true)
|
return error(it.message ?: "unknown error", echo, arrayResult = true)
|
||||||
}.getOrThrow()
|
}.getOrThrow()
|
||||||
|
val prohibitedMemberList = GroupSvc.getProhibitedMemberList(groupId.toLong())
|
||||||
|
.getOrDefault(arrayListOf())
|
||||||
|
.associate { it.memberUin to it.shutuptimestap.toLong() }
|
||||||
return ok(arrayListOf<SimpleTroopMemberInfo>().apply {
|
return ok(arrayListOf<SimpleTroopMemberInfo>().apply {
|
||||||
memberList.forEach { info ->
|
memberList.forEach { info ->
|
||||||
if (info.memberuin != "0") {
|
if (info.memberuin != "0") {
|
||||||
@ -59,7 +61,9 @@ internal object GetTroopMemberList : IActionHandler() {
|
|||||||
unfriendly = false,
|
unfriendly = false,
|
||||||
title = info.mUniqueTitle ?: "",
|
title = info.mUniqueTitle ?: "",
|
||||||
titleExpireTime = info.mUniqueTitleExpire,
|
titleExpireTime = info.mUniqueTitleExpire,
|
||||||
cardChangeable = GroupSvc.isAdmin(groupId)
|
cardChangeable = GroupSvc.isAdmin(groupId),
|
||||||
|
age = 0,
|
||||||
|
shutUpTimestamp = prohibitedMemberList[info.memberuin.toLong()] ?: 0L
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
@ -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"
|
|
||||||
}
|
|
@ -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"
|
||||||
|
}
|
@ -3,17 +3,22 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
|
|||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||||
|
import moe.fuqiuluo.shamrock.tools.jsonArray
|
||||||
|
|
||||||
internal object SendGroupMessage: IActionHandler() {
|
internal object SendGroupMessage: IActionHandler() {
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
val groupId = session.getString("group_id")
|
val groupId = session.getString("group_id")
|
||||||
|
val retryCnt = session.getIntOrNull("retry_cnt")
|
||||||
return if (session.isString("message")) {
|
return if (session.isString("message")) {
|
||||||
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
||||||
val message = session.getString("message")
|
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)
|
||||||
|
} else if (session.isObject("message")) {
|
||||||
|
val message = session.getObject("message")
|
||||||
|
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, listOf( message ).jsonArray, session.echo, retryCnt = retryCnt ?: 3)
|
||||||
} else {
|
} else {
|
||||||
val message = session.getArray("message")
|
val message = session.getArray("message")
|
||||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, session.echo)
|
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, session.echo, retryCnt = retryCnt ?: 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
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.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.tools.EmptyJsonString
|
||||||
|
|
||||||
|
internal object SendGroupNotice: IActionHandler() {
|
||||||
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
|
val groupId = session.getLong("group_id")
|
||||||
|
val text = session.getString("content")
|
||||||
|
val image = session.getStringOrNull("image")
|
||||||
|
return invoke(groupId, text, image, session.echo)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend operator fun invoke(groupId: Long, text: String, image: String?, echo: JsonElement = EmptyJsonString): String {
|
||||||
|
val groupAnnouncementMessageImage = if (image != null) {
|
||||||
|
GroupSvc.uploadImageTroopNotice(image).onFailure {
|
||||||
|
LogCenter.log("上传群公告图片失败:${it.message}", Level.WARN)
|
||||||
|
}.getOrNull()
|
||||||
|
} else null
|
||||||
|
val announcements = GroupSvc.addQunNotice(groupId, text, groupAnnouncementMessageImage)
|
||||||
|
if (announcements.isSuccess) {
|
||||||
|
return ok(announcements.getOrNull(), echo)
|
||||||
|
}
|
||||||
|
return logic(announcements.exceptionOrNull()?.message ?: "", echo)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override val requiredParams: Array<String> = arrayOf("group_id", "content")
|
||||||
|
|
||||||
|
override val alias: Array<String> = arrayOf("send_group_notice")
|
||||||
|
override fun path(): String = "_send_group_notice"
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
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.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.tools.EmptyJsonString
|
||||||
|
|
||||||
|
internal object SendGroupSign: 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 ret = GroupSvc.groupSign(groupId)
|
||||||
|
return if (ret.isSuccess) {
|
||||||
|
ok(ret.getOrNull() ?: "", echo)
|
||||||
|
} else {
|
||||||
|
logic(ret.exceptionOrNull()?.message ?: "", echo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val requiredParams: Array<String> = arrayOf("group_id")
|
||||||
|
override fun path(): String = "send_group_sign"
|
||||||
|
}
|
@ -14,16 +14,21 @@ import moe.fuqiuluo.shamrock.tools.json
|
|||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||||
|
import moe.fuqiuluo.shamrock.tools.jsonArray
|
||||||
|
|
||||||
internal object SendMessage: IActionHandler() {
|
internal object SendMessage: IActionHandler() {
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
val detailType = session.getStringOrNull("detail_type") ?: session.getStringOrNull("message_type")
|
val detailType = session.getStringOrNull("detail_type") ?: session.getStringOrNull("message_type")
|
||||||
try {
|
try {
|
||||||
var chatType = detailType?.let {
|
val chatType = detailType?.let {
|
||||||
MessageHelper.obtainMessageTypeByDetailType(it)
|
MessageHelper.obtainMessageTypeByDetailType(it)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
if (session.has("user_id")) {
|
if (session.has("user_id")) {
|
||||||
MsgConstant.KCHATTYPEC2C
|
if (session.has("group_id")) {
|
||||||
|
MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||||
|
} else {
|
||||||
|
MsgConstant.KCHATTYPEC2C
|
||||||
|
}
|
||||||
} else if (session.has("group_id")) {
|
} else if (session.has("group_id")) {
|
||||||
MsgConstant.KCHATTYPEGROUP
|
MsgConstant.KCHATTYPEGROUP
|
||||||
} else {
|
} else {
|
||||||
@ -32,24 +37,25 @@ internal object SendMessage: IActionHandler() {
|
|||||||
}
|
}
|
||||||
val peerId = when(chatType) {
|
val peerId = when(chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
|
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)
|
MsgConstant.KCHATTYPEC2C -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo)
|
||||||
else -> error("unknown chat type: $chatType")
|
else -> error("unknown chat type: $chatType")
|
||||||
}
|
}
|
||||||
var fromId = peerId
|
val retryCnt = session.getIntOrNull("retry_cnt")
|
||||||
if (chatType == MsgConstant.KCHATTYPEC2C) {
|
|
||||||
val groupId = session.getStringOrNull("group_id")
|
|
||||||
if (groupId != null) {
|
|
||||||
chatType = MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
|
||||||
fromId = groupId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return if (session.isString("message")) {
|
return if (session.isString("message")) {
|
||||||
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
||||||
val message = session.getString("message")
|
val message = session.getString("message")
|
||||||
invoke(chatType, peerId, message, autoEscape, echo = session.echo, fromId = fromId)
|
invoke(chatType, peerId, message, autoEscape, echo = session.echo, fromId = fromId, retryCnt = retryCnt ?: 3)
|
||||||
} else {
|
} else if (session.isArray("message")) {
|
||||||
val message = session.getArray("message")
|
val message = session.getArray("message")
|
||||||
invoke(chatType, peerId, message, session.echo, fromId = fromId)
|
invoke(chatType, peerId, message, session.echo, fromId = fromId, retryCnt ?: 3)
|
||||||
|
} else {
|
||||||
|
val message = session.getObject("message")
|
||||||
|
invoke(chatType, peerId, listOf( message ).jsonArray, session.echo, fromId = fromId, retryCnt ?: 3)
|
||||||
}
|
}
|
||||||
} catch (e: ParamsException) {
|
} catch (e: ParamsException) {
|
||||||
return noParam(e.message!!, session.echo)
|
return noParam(e.message!!, session.echo)
|
||||||
@ -65,45 +71,61 @@ internal object SendMessage: IActionHandler() {
|
|||||||
message: String,
|
message: String,
|
||||||
autoEscape: Boolean,
|
autoEscape: Boolean,
|
||||||
fromId: String = peerId,
|
fromId: String = peerId,
|
||||||
|
retryCnt: Int,
|
||||||
echo: JsonElement = EmptyJsonString
|
echo: JsonElement = EmptyJsonString
|
||||||
): String {
|
): String {
|
||||||
//if (!ContactHelper.checkContactAvailable(chatType, peerId)) {
|
//if (!ContactHelper.checkContactAvailable(chatType, peerId)) {
|
||||||
// return logic("contact is not found", echo = echo)
|
// return logic("contact is not found", echo = echo)
|
||||||
//}
|
//}
|
||||||
val result = if (autoEscape) {
|
val result = if (autoEscape) {
|
||||||
MsgSvc.sendToAio(chatType, peerId, arrayListOf(message).json, fromId = fromId)
|
MsgSvc.sendToAio(chatType, peerId, listOf(
|
||||||
|
mapOf(
|
||||||
|
"type" to "text",
|
||||||
|
"data" to mapOf(
|
||||||
|
"text" to message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).json, fromId = fromId)
|
||||||
} else {
|
} else {
|
||||||
val msg = MessageHelper.decodeCQCode(message)
|
val msg = MessageHelper.decodeCQCode(message)
|
||||||
if (msg.isEmpty()) {
|
if (msg.isEmpty()) {
|
||||||
LogCenter.log("CQ码不合法", Level.WARN)
|
LogCenter.log("CQ码不合法", Level.WARN)
|
||||||
return logic("CQCode is illegal", echo)
|
return logic("CQCode is illegal", echo)
|
||||||
} else {
|
} else {
|
||||||
MsgSvc.sendToAio(chatType, peerId, msg, fromId = fromId)
|
MsgSvc.sendToAio(chatType, peerId, msg, fromId = fromId, retryCnt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result.first <= 0) {
|
if (result.isFailure) {
|
||||||
|
return logic(result.exceptionOrNull()?.message ?: "", echo)
|
||||||
|
}
|
||||||
|
val pair = result.getOrNull() ?: Pair(0L, 0)
|
||||||
|
if (pair.first <= 0) {
|
||||||
return logic("send message failed", echo = echo)
|
return logic("send message failed", echo = echo)
|
||||||
}
|
}
|
||||||
return ok(MessageResult(
|
return ok(MessageResult(
|
||||||
msgId = result.second,
|
msgId = pair.second,
|
||||||
time = (result.first * 0.001).toLong()
|
time = (pair.first * 0.001).toLong()
|
||||||
), echo = echo)
|
), echo = echo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 消息段格式消息
|
// 消息段格式消息
|
||||||
suspend operator fun invoke(
|
suspend operator fun invoke(
|
||||||
chatType: Int, peerId: String, message: JsonArray, echo: JsonElement = EmptyJsonString, fromId: String = peerId
|
chatType: Int, peerId: String, message: JsonArray, echo: JsonElement = EmptyJsonString, fromId: String = peerId, retryCnt: Int
|
||||||
): String {
|
): String {
|
||||||
//if (!ContactHelper.checkContactAvailable(chatType, peerId)) {
|
//if (!ContactHelper.checkContactAvailable(chatType, peerId)) {
|
||||||
// return logic("contact is not found", echo = echo)
|
// return logic("contact is not found", echo = echo)
|
||||||
//}
|
//}
|
||||||
val result = MsgSvc.sendToAio(chatType, peerId, message, fromId = fromId)
|
val result = MsgSvc.sendToAio(chatType, peerId, message, fromId = fromId, retryCnt)
|
||||||
if (result.first <= 0) {
|
if (result.isFailure) {
|
||||||
|
return logic(result.exceptionOrNull()?.message ?: "", echo)
|
||||||
|
}
|
||||||
|
val pair = result.getOrNull() ?: Pair(0L, 0)
|
||||||
|
if (pair.first <= 0) {
|
||||||
return logic("send message failed", echo = echo)
|
return logic("send message failed", echo = echo)
|
||||||
}
|
}
|
||||||
return ok(MessageResult(
|
return ok(MessageResult(
|
||||||
msgId = result.second,
|
msgId = pair.second,
|
||||||
time = (result.first * 0.001).toLong()
|
time = (pair.first * 0.001).toLong()
|
||||||
), echo)
|
), echo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 SendPrivateForwardMessage : IActionHandler() {
|
||||||
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
|
val userId = session.getString("user_id")
|
||||||
|
return if (session.isArray("messages")) {
|
||||||
|
val messages = session.getArray("messages")
|
||||||
|
SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId, messages, echo = session.echo)
|
||||||
|
} else {
|
||||||
|
logic("未知格式合并转发消息", session.echo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val requiredParams: Array<String> = arrayOf("messages", "user_id")
|
||||||
|
|
||||||
|
override fun path(): String = "send_private_forward_msg"
|
||||||
|
}
|
@ -3,31 +3,45 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
|
|||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||||
|
import moe.fuqiuluo.shamrock.tools.jsonArray
|
||||||
|
|
||||||
internal object SendPrivateMessage: IActionHandler() {
|
internal object SendPrivateMessage: IActionHandler() {
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
val userId = session.getString("user_id")
|
val userId = session.getString("user_id")
|
||||||
val groupId = session.getStringOrNull("group_id")
|
val groupId = session.getStringOrNull("group_id")
|
||||||
val chatTYpe = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||||
|
val retryCnt = session.getIntOrNull("retry_cnt")
|
||||||
return if (session.isString("message")) {
|
return if (session.isString("message")) {
|
||||||
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
||||||
val message = session.getString("message")
|
val message = session.getString("message")
|
||||||
SendMessage.invoke(
|
SendMessage.invoke(
|
||||||
chatType = chatTYpe,
|
chatType = chatType,
|
||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = message,
|
message = message,
|
||||||
autoEscape = autoEscape,
|
autoEscape = autoEscape,
|
||||||
echo = session.echo,
|
echo = session.echo,
|
||||||
fromId = groupId ?: userId
|
fromId = groupId ?: userId,
|
||||||
|
retryCnt = retryCnt ?: 3
|
||||||
)
|
)
|
||||||
} else {
|
} else if (session.isArray("message")) {
|
||||||
val message = session.getArray("message")
|
val message = session.getArray("message")
|
||||||
SendMessage(
|
SendMessage(
|
||||||
chatType = chatTYpe,
|
chatType = chatType,
|
||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = message,
|
message = message,
|
||||||
echo = session.echo,
|
echo = session.echo,
|
||||||
fromId = groupId ?: userId
|
fromId = groupId ?: userId,
|
||||||
|
retryCnt = retryCnt ?: 3
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val message = session.getObject("message")
|
||||||
|
SendMessage(
|
||||||
|
chatType = chatType,
|
||||||
|
peerId = userId,
|
||||||
|
message = listOf( message ).jsonArray,
|
||||||
|
echo = session.echo,
|
||||||
|
fromId = groupId ?: userId,
|
||||||
|
retryCnt = retryCnt ?: 3
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user