mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
85 Commits
1.0.7
...
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 |
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 }}
|
|
@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
☘ 基于 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/)!
|
||||||
@ -94,7 +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/
|
||||||
|
|
||||||
[hook-system]: https://github.com/whitechi73/OpenShamrock/blob/master/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/FuckAMS.kt
|
[hook-system]: https://github.com/whitechi73/OpenShamrock/blob/master/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/KeepAlive.kt
|
||||||
|
|
||||||
[voice-support]: https://whitechi73.github.io/OpenShamrock/advanced/voice.html
|
[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.7-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"
|
||||||
@ -19,7 +15,6 @@
|
|||||||
android:theme="@style/Theme.Shamrock"
|
android:theme="@style/Theme.Shamrock"
|
||||||
android:zygotePreloadName="@string/app_name"
|
android:zygotePreloadName="@string/app_name"
|
||||||
android:multiArch="true"
|
android:multiArch="true"
|
||||||
android:extractNativeLibs="true"
|
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
@ -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,14 +99,14 @@ 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 {
|
} else {
|
||||||
kv.emplace("_type", cache);
|
kv.emplace("_type", cache);
|
||||||
@ -114,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,7 +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
|
@Suppress("LocalVariableName") val LocalString = LocalString.init()
|
||||||
|
|
||||||
if (!AppRuntime.isInit) {
|
if (!AppRuntime.isInit) {
|
||||||
AppRuntime.state = remember {
|
AppRuntime.state = remember {
|
||||||
@ -147,7 +148,7 @@ private fun AppMainView() {
|
|||||||
|
|
||||||
AppRuntime.requestCount = remember { mutableIntStateOf(0) }
|
AppRuntime.requestCount = remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
AppRuntime.isInit = false
|
AppRuntime.isInit = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
|
@ -279,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?> {
|
||||||
@ -321,6 +321,7 @@ object ShamrockConfig {
|
|||||||
"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),
|
"alive_reply" to preferences.getBoolean("alive_reply", false),
|
||||||
|
"enable_sync_msg_as_sent_msg" to preferences.getBoolean("enable_sync_msg_as_sent_msg", false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,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}]。")
|
||||||
|
@ -275,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()
|
||||||
@ -164,4 +166,14 @@ open class VarString(
|
|||||||
|
|
||||||
var persistentText: String,
|
var persistentText: String,
|
||||||
var persistentTextDesc: String
|
var persistentTextDesc: String
|
||||||
)
|
) {
|
||||||
|
private var inited = false
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun init(): VarString {
|
||||||
|
if (inited) return this
|
||||||
|
|
||||||
|
inited = true
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
@ -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 += ""
|
||||||
|
@ -15,6 +15,8 @@ static std::vector<std::string> qemu_detect_props = {
|
|||||||
|
|
||||||
static int (*backup_system_property_get)(const char *name, char *value);
|
static int (*backup_system_property_get)(const char *name, char *value);
|
||||||
static FILE* (*backup_fopen)(const char *filename, const char *mode);
|
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);
|
//int fake_system_property_get(const char *name, char *value);
|
||||||
//FILE* fake_fopen(const char *filename, const char *mode);
|
//FILE* fake_fopen(const char *filename, const char *mode);
|
||||||
|
@ -71,6 +71,7 @@ int fake_system_property_get(const char *name, char *value) {
|
|||||||
|| strstr(value, "unknown")
|
|| strstr(value, "unknown")
|
||||||
|| strstr(value, "emulator")
|
|| strstr(value, "emulator")
|
||||||
|| strstr(value, "vbox")
|
|| strstr(value, "vbox")
|
||||||
|
|| strstr(value, "nox") //部分NoxAppPlayer
|
||||||
|| strstr(value, "genymotion")
|
|| strstr(value, "genymotion")
|
||||||
|| strstr(value, "goldfish")) {
|
|| strstr(value, "goldfish")) {
|
||||||
strcpy(value, "qcom");
|
strcpy(value, "qcom");
|
||||||
@ -84,22 +85,84 @@ int fake_system_property_get(const char *name, char *value) {
|
|||||||
return backup_system_property_get(name, value);
|
return backup_system_property_get(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FILE* fake_fopen(const char *filename, const char *mode) {
|
FILE* fake_fopen(const char *filename, const char *mode) {
|
||||||
if (strstr(filename, "qemu_pipe")) {
|
if (strstr(filename, "qemu_pipe")) {
|
||||||
LOGI("[Shamrock] bypass qemu detection");
|
LOGI("[Shamrock] bypass qemu detection");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strstr(filename, "libhoudini.so")) {
|
const char* emuSpecFile[] = {
|
||||||
LOGI("[Shamrock] bypass emu detection");
|
"libhoudini.so",
|
||||||
return nullptr;
|
"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);
|
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) {
|
void on_library_loaded(const char *name, void *handle) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -119,5 +182,8 @@ Java_moe_fuqiuluo_shamrock_xposed_actions_AntiDetection_antiNativeDetections(JNI
|
|||||||
if (hook_function == nullptr) return false;
|
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*) __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*) 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
@ -46,7 +47,11 @@ 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.helper.MessageHelper
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.EssenceMessage
|
import moe.fuqiuluo.shamrock.remote.service.data.EssenceMessage
|
||||||
@ -65,13 +70,19 @@ 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.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
|
||||||
@ -94,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)
|
||||||
@ -420,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 {
|
||||||
@ -526,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
|
||||||
@ -805,12 +867,13 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
senderId = obj["u"].asLong,
|
senderId = obj["u"].asLong,
|
||||||
publishTime = obj["pubt"].asLong,
|
publishTime = obj["pubt"].asLong,
|
||||||
message = GroupAnnouncementMessage(
|
message = GroupAnnouncementMessage(
|
||||||
text = obj["msg"].asJsonObject["text"].asString,
|
// text = obj["msg"].asJsonObject["text"].asString,
|
||||||
images = obj["msg"].asJsonObject["pics"].asJsonArrayOrNull?.map {
|
text = fromHtml(obj["msg"].asJsonObject["text"].asString),
|
||||||
|
images = obj["msg"].asJsonObject["pics"].asJsonArrayOrNull?.map { pic ->
|
||||||
GroupAnnouncementMessageImage(
|
GroupAnnouncementMessageImage(
|
||||||
id = it.jsonObject["id"].asString,
|
id = pic.jsonObject["id"].asString,
|
||||||
width = it.jsonObject["w"].asString,
|
width = pic.jsonObject["w"].asString,
|
||||||
height = it.jsonObject["h"].asString,
|
height = pic.jsonObject["h"].asString,
|
||||||
)
|
)
|
||||||
} ?: ArrayList()
|
} ?: ArrayList()
|
||||||
)
|
)
|
||||||
@ -821,6 +884,14 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
suspend fun uploadImageTroopNotice(image: String): Result<GroupAnnouncementMessageImage> {
|
suspend fun uploadImageTroopNotice(image: String): Result<GroupAnnouncementMessageImage> {
|
||||||
val file = FileUtils.parseAndSave(image)
|
val file = FileUtils.parseAndSave(image)
|
||||||
@ -907,4 +978,27 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
Result.failure(Exception(body.jsonObject["em"].asStringOrNull))
|
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(" ")}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -176,9 +176,6 @@ internal object MsgSvc: BaseSvc() {
|
|||||||
fromId: String = peedId,
|
fromId: String = peedId,
|
||||||
retryCnt: Int = 3
|
retryCnt: Int = 3
|
||||||
): Result<Pair<Long, Int>> {
|
): Result<Pair<Long, Int>> {
|
||||||
//LogCenter.log(message.toString(), Level.ERROR)
|
|
||||||
//callback.msgHash = result.second 什么垃圾代码,万一cb比你快,你不就寄了?
|
|
||||||
|
|
||||||
// 主动临时消息
|
// 主动临时消息
|
||||||
when (chatType) {
|
when (chatType) {
|
||||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||||
@ -188,13 +185,7 @@ internal object MsgSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = MessageHelper.sendMessageWithoutMsgId(
|
val result = MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0))
|
||||||
chatType,
|
|
||||||
peedId,
|
|
||||||
message,
|
|
||||||
fromId,
|
|
||||||
MessageCallback(peedId, 0)
|
|
||||||
)
|
|
||||||
return if (result.isFailure
|
return if (result.isFailure
|
||||||
&& result.exceptionOrNull()?.javaClass == SendMsgException::class.java
|
&& result.exceptionOrNull()?.javaClass == SendMsgException::class.java
|
||||||
&& retryCnt > 0) {
|
&& retryCnt > 0) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
)
|
)
|
@ -274,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("不存在该消息映射,无法回复消息"))
|
||||||
|
|
||||||
@ -627,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())
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -324,14 +324,15 @@ internal sealed class MessageElemConverter: IMessageConvert {
|
|||||||
val notify = tip.jsonGrayTipElement
|
val notify = tip.jsonGrayTipElement
|
||||||
when(notify.busiId) {
|
when(notify.busiId) {
|
||||||
/* 新人入群 */ 17L, /* 群戳一戳 */1061L,
|
/* 新人入群 */ 17L, /* 群戳一戳 */1061L,
|
||||||
/* 群撤回 */1014L, /* 群设精消息 */2401L -> {}
|
/* 群撤回 */1014L, /* 群设精消息 */2401L,
|
||||||
|
/* 群头衔 */2407L -> {}
|
||||||
else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN)
|
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) {
|
||||||
/* 群戳一戳 */1061L -> {}
|
/* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
|
||||||
else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
|
else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -412,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 {
|
||||||
|
@ -9,6 +9,7 @@ import com.tencent.qqnt.msg.api.IMsgService
|
|||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
@ -38,7 +39,11 @@ 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>
|
||||||
@ -59,6 +64,12 @@ internal object MessageHelper {
|
|||||||
}.second.filter {
|
}.second.filter {
|
||||||
it.elementType != -1
|
it.elementType != -1
|
||||||
} as ArrayList<MsgElement>
|
} as ArrayList<MsgElement>
|
||||||
|
|
||||||
|
// ActionMsg No Care
|
||||||
|
if (msg.isEmpty()) {
|
||||||
|
return Result.success(System.currentTimeMillis() to 0)
|
||||||
|
}
|
||||||
|
|
||||||
val totalSize = msg.filter {
|
val totalSize = msg.filter {
|
||||||
it.elementType == MsgConstant.KELEMTYPEPIC ||
|
it.elementType == MsgConstant.KELEMTYPEPIC ||
|
||||||
it.elementType == MsgConstant.KELEMTYPEPTT ||
|
it.elementType == MsgConstant.KELEMTYPEPTT ||
|
||||||
@ -67,11 +78,11 @@ internal object MessageHelper {
|
|||||||
(it.picElement?.fileSize ?: 0) + (it.pttElement?.fileSize
|
(it.picElement?.fileSize ?: 0) + (it.pttElement?.fileSize
|
||||||
?: 0) + (it.videoElement?.fileSize ?: 0)
|
?: 0) + (it.videoElement?.fileSize ?: 0)
|
||||||
}.reduceOrNull { a, b -> a + b } ?: 0
|
}.reduceOrNull { a, b -> a + b } ?: 0
|
||||||
|
val estimateTime = (totalSize / (300 * 1024)) * 1000 + 2000
|
||||||
|
|
||||||
val estimateTime = (totalSize / (300 * 1024)) * 1000 + 5000
|
|
||||||
lateinit var sendResultPair: Pair<Long, Int>
|
lateinit var sendResultPair: Pair<Long, Int>
|
||||||
val sendRet = withTimeoutOrNull<Pair<Int, String>>(estimateTime) {
|
val sendRet = withTimeoutOrNull<Pair<Int, String>>(estimateTime) {
|
||||||
suspendCoroutine {
|
suspendCancellableCoroutine {
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
sendResultPair = sendMessageWithoutMsgId(
|
sendResultPair = sendMessageWithoutMsgId(
|
||||||
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,7 +31,8 @@ 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, GetEssenceMessageList, GetGroupNotice, SendGroupNotice,
|
GetGroupSystemMsg, GetProhibitedMemberList, GetEssenceMessageList, GetGroupNotice, SendGroupNotice, SendGroupSign,
|
||||||
|
GetGroupRemainAtAllRemain,
|
||||||
|
|
||||||
// MSG ACTIONS
|
// MSG ACTIONS
|
||||||
SendMessage, DeleteMessage, GetMsg, GetForwardMsg, SendPrivateForwardMessage, SendGroupMessage, SendPrivateMessage,
|
SendMessage, DeleteMessage, GetMsg, GetForwardMsg, SendPrivateForwardMessage, SendGroupMessage, SendPrivateMessage,
|
||||||
@ -42,17 +43,20 @@ internal object ActionManager {
|
|||||||
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
|
||||||
|
@ -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,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,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"
|
||||||
|
}
|
@ -61,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))
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,7 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
|
|||||||
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo
|
import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.*
|
||||||
import kotlinx.serialization.json.JsonElement
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
|
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
|
||||||
@ -18,19 +16,6 @@ import moe.fuqiuluo.shamrock.remote.service.data.ForwardMessageResult
|
|||||||
import moe.fuqiuluo.shamrock.tools.*
|
import moe.fuqiuluo.shamrock.tools.*
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
|
|
||||||
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 SendForwardMessage : IActionHandler() {
|
internal object SendForwardMessage : 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")
|
||||||
@ -39,31 +24,33 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
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 {
|
||||||
return noParam("detail_type/message_type", session.echo)
|
return noParam("detail_type/message_type", session.echo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val peerId = when (chatType) {
|
val peerId = when(chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam(
|
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
|
||||||
"group_id",
|
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo)
|
||||||
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")
|
||||||
}
|
}
|
||||||
if (session.isArray("messages")) {
|
val fromId = when(chatType) {
|
||||||
val messages = session.getArray("messages")
|
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
|
||||||
invoke(chatType, peerId, messages, echo = 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)
|
||||||
}
|
}
|
||||||
return logic("未知格式合并转发消息", session.echo)
|
|
||||||
} catch (e: ParamsException) {
|
} catch (e: ParamsException) {
|
||||||
return noParam(e.message!!, session.echo)
|
return noParam(e.message!!, session.echo)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
@ -74,7 +61,8 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
suspend operator fun invoke(
|
suspend operator fun invoke(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
peerId: String,
|
peerId: String,
|
||||||
message: JsonArray,
|
messages: JsonArray,
|
||||||
|
fromId: String = peerId,
|
||||||
echo: JsonElement = EmptyJsonString
|
echo: JsonElement = EmptyJsonString
|
||||||
): String {
|
): String {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
@ -83,63 +71,91 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
val msgService = sessionService.msgService
|
val msgService = sessionService.msgService
|
||||||
val selfUin = TicketSvc.getUin()
|
val selfUin = TicketSvc.getUin()
|
||||||
|
|
||||||
val nodes = message.map {
|
val multiNodes = messages.map {
|
||||||
if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段
|
if (it.asJsonObject["type"].asStringOrNull != "node") {
|
||||||
it.asJsonObject["data"].asJsonObject.let { data ->
|
LogCenter.log("包含非node类型节点", Level.WARN)
|
||||||
if (data.containsKey("content")) {
|
return@map null
|
||||||
if (data["content"] is JsonArray) {
|
|
||||||
data["content"].asJsonArray.forEach { msg ->
|
|
||||||
if (msg.asJsonObject["type"].asStringOrNull == "node") {
|
|
||||||
LogCenter.log("合并转发消息不支持嵌套", Level.WARN)
|
|
||||||
return@map ForwardMsgNode.EmptyNode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ForwardMsgNode.MessageNode(
|
|
||||||
name = data["name"].asStringOrNull ?: "",
|
|
||||||
content = data["content"]
|
|
||||||
)
|
|
||||||
} else ForwardMsgNode.MessageIdNode(data["id"].asInt)
|
|
||||||
}
|
}
|
||||||
}.map {
|
if (it.asJsonObject["data"] !is JsonObject) {
|
||||||
if (it is ForwardMsgNode.MessageIdNode) {
|
LogCenter.log("data字段错误", Level.WARN)
|
||||||
val recordResult = MsgSvc.getMsg(it.id)
|
return@map null
|
||||||
if (!recordResult.isFailure) {
|
}
|
||||||
ForwardMsgNode.EmptyNode
|
it.asJsonObject["data"].asJsonObject.let { data ->
|
||||||
} else {
|
if (data.containsKey("id")) {
|
||||||
val record = recordResult.getOrThrow()
|
val record = MsgSvc.getMsg(data["id"].asInt).getOrNull()
|
||||||
ForwardMsgNode.MessageNode(
|
if (record == null) {
|
||||||
name = record.peerName,
|
LogCenter.log("合并转发消息节点消息获取失败:${data["id"]}", Level.WARN)
|
||||||
content = record.toSegments().map { segment ->
|
return@map null
|
||||||
|
} else {
|
||||||
|
record.peerName to record.toSegments().map { segment ->
|
||||||
segment.toJson()
|
segment.toJson()
|
||||||
}.json
|
}.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
|
||||||
}
|
}
|
||||||
} else {
|
}.let { node ->
|
||||||
it as ForwardMsgNode.MessageNode
|
val content = node.second.map { msg ->
|
||||||
}
|
when (msg.asJsonObject["type"].asStringOrNull ?: "text") {
|
||||||
}.filter {
|
"at" -> {
|
||||||
it.content != null
|
buildJsonObject {
|
||||||
}.map { node ->
|
put("type", "text")
|
||||||
val result = MessageHelper.sendMessageNoCb(MsgConstant.KCHATTYPEC2C, selfUin, node.content.let { msg ->
|
putJsonObject("data") {
|
||||||
when (msg) {
|
put(
|
||||||
is JsonArray -> msg
|
"text", "@${
|
||||||
is JsonObject -> listOf(msg).jsonArray
|
msg.asJsonObject["data"].asJsonObject["name"].asStringOrNull.ifNullOrEmpty(
|
||||||
else -> MessageHelper.decodeCQCode(msg.asString)
|
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
|
||||||
if (result.first != 0) {
|
|
||||||
LogCenter.log("合并转发消息节点消息发送失败", Level.WARN)
|
|
||||||
}
|
}
|
||||||
return@map result.second
|
}.filterNotNull()
|
||||||
}
|
|
||||||
|
|
||||||
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
|
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
|
||||||
val to = MessageHelper.generateContact(chatType, peerId)
|
val to = MessageHelper.generateContact(chatType, peerId, fromId)
|
||||||
|
|
||||||
val uniseq = MessageHelper.generateMsgId(chatType)
|
val uniseq = MessageHelper.generateMsgId(chatType)
|
||||||
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply {
|
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply {
|
||||||
nodes.forEach { add(MultiMsgInfo(it, "Anno")) }
|
multiNodes.forEach { add(MultiMsgInfo(it.first, it.second)) }
|
||||||
}.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.first))
|
}.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.first))
|
||||||
|
|
||||||
return ok(
|
return ok(
|
||||||
@ -154,7 +170,7 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
return logic("合并转发消息失败(unknown error)", echo)
|
return logic("合并转发消息失败(unknown error)", echo)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val requiredParams: Array<String> = arrayOf("message")
|
override val requiredParams: Array<String> = arrayOf("messages")
|
||||||
|
|
||||||
override fun path(): String = "send_forward_msg"
|
override fun path(): String = "send_forward_msg"
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ internal object SendGroupForwardMessage: IActionHandler() {
|
|||||||
val groupId = session.getString("group_id")
|
val groupId = session.getString("group_id")
|
||||||
return if (session.isArray("messages")) {
|
return if (session.isArray("messages")) {
|
||||||
val messages = session.getArray("messages")
|
val messages = session.getArray("messages")
|
||||||
SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId, messages, session.echo)
|
SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId, messages, echo = session.echo)
|
||||||
} else {
|
} else {
|
||||||
logic("未知格式合并转发消息", session.echo)
|
logic("未知格式合并转发消息", session.echo)
|
||||||
}
|
}
|
||||||
|
@ -8,16 +8,17 @@ 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")) {
|
} else if (session.isObject("message")) {
|
||||||
val message = session.getObject("message")
|
val message = session.getObject("message")
|
||||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, listOf( message ).jsonArray, session.echo)
|
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,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"
|
||||||
|
}
|
@ -20,11 +20,15 @@ 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 {
|
||||||
@ -33,27 +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 if (session.isArray("message")) {
|
} 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 {
|
} else {
|
||||||
val message = session.getObject("message")
|
val message = session.getObject("message")
|
||||||
invoke(chatType, peerId, listOf( message ).jsonArray, session.echo, fromId = fromId)
|
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)
|
||||||
@ -69,6 +71,7 @@ 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)) {
|
||||||
@ -89,7 +92,7 @@ internal object SendMessage: IActionHandler() {
|
|||||||
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.isFailure) {
|
if (result.isFailure) {
|
||||||
@ -107,12 +110,12 @@ internal object SendMessage: IActionHandler() {
|
|||||||
|
|
||||||
// 消息段格式消息
|
// 消息段格式消息
|
||||||
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.isFailure) {
|
if (result.isFailure) {
|
||||||
return logic(result.exceptionOrNull()?.message ?: "", echo)
|
return logic(result.exceptionOrNull()?.message ?: "", echo)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ internal object SendPrivateForwardMessage : IActionHandler() {
|
|||||||
val userId = session.getString("user_id")
|
val userId = session.getString("user_id")
|
||||||
return if (session.isArray("messages")) {
|
return if (session.isArray("messages")) {
|
||||||
val messages = session.getArray("messages")
|
val messages = session.getArray("messages")
|
||||||
SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId, messages, session.echo)
|
SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId, messages, echo = session.echo)
|
||||||
} else {
|
} else {
|
||||||
logic("未知格式合并转发消息", session.echo)
|
logic("未知格式合并转发消息", session.echo)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ internal object SendPrivateMessage: IActionHandler() {
|
|||||||
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")
|
||||||
@ -19,7 +20,8 @@ internal object SendPrivateMessage: IActionHandler() {
|
|||||||
message = message,
|
message = message,
|
||||||
autoEscape = autoEscape,
|
autoEscape = autoEscape,
|
||||||
echo = session.echo,
|
echo = session.echo,
|
||||||
fromId = groupId ?: userId
|
fromId = groupId ?: userId,
|
||||||
|
retryCnt = retryCnt ?: 3
|
||||||
)
|
)
|
||||||
} else if (session.isArray("message")) {
|
} else if (session.isArray("message")) {
|
||||||
val message = session.getArray("message")
|
val message = session.getArray("message")
|
||||||
@ -28,7 +30,8 @@ internal object SendPrivateMessage: IActionHandler() {
|
|||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = message,
|
message = message,
|
||||||
echo = session.echo,
|
echo = session.echo,
|
||||||
fromId = groupId ?: userId
|
fromId = groupId ?: userId,
|
||||||
|
retryCnt = retryCnt ?: 3
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val message = session.getObject("message")
|
val message = session.getObject("message")
|
||||||
@ -37,7 +40,8 @@ internal object SendPrivateMessage: IActionHandler() {
|
|||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = listOf( message ).jsonArray,
|
message = listOf( message ).jsonArray,
|
||||||
echo = session.echo,
|
echo = session.echo,
|
||||||
fromId = groupId ?: userId
|
fromId = groupId ?: userId,
|
||||||
|
retryCnt = retryCnt ?: 3
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ internal object SetFriendAddRequest: IActionHandler() {
|
|||||||
if (ts.toString().length < 13) {
|
if (ts.toString().length < 13) {
|
||||||
// time but not seq, query seq again
|
// time but not seq, query seq again
|
||||||
val reqs = FriendSvc.requestFriendSystemMsgNew(20, 0, 0, 1)
|
val reqs = FriendSvc.requestFriendSystemMsgNew(20, 0, 0, 1)
|
||||||
val req = reqs?.first {
|
val req = reqs?.firstOrNull {
|
||||||
it.msg_time.get() == ts
|
it.msg_time.get() == ts
|
||||||
}
|
}
|
||||||
// 好友请求seq貌似就是time*1000,查不到直接*1000
|
// 好友请求seq貌似就是time*1000,查不到直接*1000
|
||||||
|
@ -27,10 +27,10 @@ internal object SetGroupAddRequest: IActionHandler() {
|
|||||||
var reqs = GroupSvc.requestGroupSystemMsgNew(20, 1)
|
var reqs = GroupSvc.requestGroupSystemMsgNew(20, 1)
|
||||||
val riskReqs = GroupSvc.requestGroupSystemMsgNew(20, 2)
|
val riskReqs = GroupSvc.requestGroupSystemMsgNew(20, 2)
|
||||||
reqs = reqs + riskReqs
|
reqs = reqs + riskReqs
|
||||||
val req = reqs.first {
|
val req = reqs.firstOrNull {
|
||||||
it.msg_time.get() == ts
|
it.msg_time.get() == ts
|
||||||
}
|
}
|
||||||
ts = req.msg_seq?.get() ?: return error("失败:未找到该请求", echo)
|
ts = req?.msg_seq?.get() ?: return error("失败:未找到该请求", echo)
|
||||||
}
|
}
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
LogCenter.log(err.stackTraceToString(), Level.WARN)
|
LogCenter.log(err.stackTraceToString(), Level.WARN)
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.remote.api
|
||||||
|
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.response.respondText
|
||||||
|
import io.ktor.server.routing.Routing
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddImageMsg
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddTextMsg
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.handlers.FavGetItemContent
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.handlers.FavGetItemList
|
||||||
|
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
||||||
|
import moe.fuqiuluo.shamrock.tools.fetchOrThrow
|
||||||
|
import moe.fuqiuluo.shamrock.tools.getOrPost
|
||||||
|
|
||||||
|
// fav.add_rich_media_msg
|
||||||
|
|
||||||
|
fun Routing.fav() {
|
||||||
|
getOrPost("/fav/add_rich_media_msg") {
|
||||||
|
val uin = call.fetchOrThrow("user_id").toLong()
|
||||||
|
val nickName = call.fetchOrThrow("nick")
|
||||||
|
val time = call.fetchOrNull("time")?.toLong() ?: System.currentTimeMillis()
|
||||||
|
val content = call.fetchOrThrow("content")
|
||||||
|
val groupName = call.fetchOrNull("group_name") ?: ""
|
||||||
|
val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L
|
||||||
|
call.respondText(FavAddTextMsg(uin, nickName, time, content, groupName, groupId), ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrPost("/fav/add_image_msg") {
|
||||||
|
val uin = call.fetchOrThrow("user_id").toLong()
|
||||||
|
val nickName = call.fetchOrThrow("nick")
|
||||||
|
val file = call.fetchOrThrow("file")
|
||||||
|
val groupName = call.fetchOrNull("groupName") ?: ""
|
||||||
|
val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L
|
||||||
|
call.respondText(FavAddImageMsg(uin, nickName, file, groupName, groupId), ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrPost("/fav/get_item_content") {
|
||||||
|
val id = call.fetchOrThrow("id")
|
||||||
|
call.respondText(FavGetItemContent(id), ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrPost("/fav/get_item_list") {
|
||||||
|
val category = call.fetchOrThrow("category").toInt()
|
||||||
|
val startPos = call.fetchOrThrow("start_pos").toInt()
|
||||||
|
val pageSize = call.fetchOrThrow("page_size").toInt()
|
||||||
|
call.respondText(FavGetItemList(category, startPos, pageSize), ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import io.ktor.server.response.respondText
|
|||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
import io.ktor.server.routing.get
|
import io.ktor.server.routing.get
|
||||||
import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendList
|
import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendList
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendSystemMsg
|
||||||
import moe.fuqiuluo.shamrock.remote.action.handlers.GetStrangerInfo
|
import moe.fuqiuluo.shamrock.remote.action.handlers.GetStrangerInfo
|
||||||
import moe.fuqiuluo.shamrock.remote.action.handlers.IsBlackListUin
|
import moe.fuqiuluo.shamrock.remote.action.handlers.IsBlackListUin
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchGetOrThrow
|
import moe.fuqiuluo.shamrock.tools.fetchGetOrThrow
|
||||||
@ -30,4 +31,9 @@ fun Routing.friendAction() {
|
|||||||
val uin = fetchOrThrow("user_id")
|
val uin = fetchOrThrow("user_id")
|
||||||
call.respondText(IsBlackListUin(uin), ContentType.Application.Json)
|
call.respondText(IsBlackListUin(uin), ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrPost("/get_friend_system_msg") {
|
||||||
|
call.respondText(GetFriendSystemMsg(), ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -140,4 +140,14 @@ fun Routing.troopAction() {
|
|||||||
call.respondText(SendGroupNotice(groupId, text, image), ContentType.Application.Json)
|
call.respondText(SendGroupNotice(groupId, text, image), ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrPost("/send_group_sign") {
|
||||||
|
val groupId = fetchOrThrow("group_id").toLong()
|
||||||
|
call.respondText(SendGroupSign(groupId), ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrPost("/get_group_at_all_remain") {
|
||||||
|
val groupId = fetchOrThrow("group_id").toLong()
|
||||||
|
call.respondText(GetGroupRemainAtAllRemain(groupId), ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote.api
|
package moe.fuqiuluo.shamrock.remote.api
|
||||||
|
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.server.application.ApplicationCall
|
||||||
import io.ktor.server.application.call
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.request.httpVersion
|
import io.ktor.server.request.httpVersion
|
||||||
import io.ktor.server.response.respondText
|
import io.ktor.server.response.respondText
|
||||||
@ -8,8 +9,12 @@ import io.ktor.server.routing.Routing
|
|||||||
import io.ktor.server.routing.get
|
import io.ktor.server.routing.get
|
||||||
import io.ktor.server.routing.post
|
import io.ktor.server.routing.post
|
||||||
import io.ktor.server.routing.route
|
import io.ktor.server.routing.route
|
||||||
|
import io.ktor.util.pipeline.PipelineContext
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
import moe.fuqiuluo.shamrock.remote.HTTPServer
|
import moe.fuqiuluo.shamrock.remote.HTTPServer
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionManager
|
import moe.fuqiuluo.shamrock.remote.action.ActionManager
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
@ -18,12 +23,17 @@ import moe.fuqiuluo.shamrock.remote.entries.EmptyObject
|
|||||||
import moe.fuqiuluo.shamrock.remote.entries.IndexData
|
import moe.fuqiuluo.shamrock.remote.entries.IndexData
|
||||||
import moe.fuqiuluo.shamrock.remote.entries.Status
|
import moe.fuqiuluo.shamrock.remote.entries.Status
|
||||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
|
||||||
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asJsonObjectOrNull
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asString
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchOrThrow
|
import moe.fuqiuluo.shamrock.tools.fetchOrThrow
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchPostJsonElement
|
import moe.fuqiuluo.shamrock.tools.fetchPostJsonElement
|
||||||
|
import moe.fuqiuluo.shamrock.tools.fetchPostJsonElementOrNull
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchPostJsonObject
|
import moe.fuqiuluo.shamrock.tools.fetchPostJsonObject
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchPostJsonObjectOrNull
|
import moe.fuqiuluo.shamrock.tools.fetchPostJsonObjectOrNull
|
||||||
import moe.fuqiuluo.shamrock.tools.isJsonArray
|
import moe.fuqiuluo.shamrock.tools.isJsonArray
|
||||||
|
import moe.fuqiuluo.shamrock.tools.isJsonData
|
||||||
import moe.fuqiuluo.shamrock.tools.isJsonObject
|
import moe.fuqiuluo.shamrock.tools.isJsonObject
|
||||||
import moe.fuqiuluo.shamrock.tools.isJsonString
|
import moe.fuqiuluo.shamrock.tools.isJsonString
|
||||||
import moe.fuqiuluo.shamrock.tools.json
|
import moe.fuqiuluo.shamrock.tools.json
|
||||||
@ -39,6 +49,31 @@ data class OldApiResult<T>(
|
|||||||
val data: T? = null
|
val data: T? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun PipelineContext<Unit, ApplicationCall>.handleAsJsonObject(data: JsonObject) {
|
||||||
|
val action = data["action"].asString
|
||||||
|
val echo = data["echo"] ?: EmptyJsonString
|
||||||
|
call.attributes.put(ECHO_KEY, echo)
|
||||||
|
|
||||||
|
val params = data["params"].asJsonObjectOrNull ?: EmptyJsonObject
|
||||||
|
|
||||||
|
val handler = ActionManager[action]
|
||||||
|
if (handler == null) {
|
||||||
|
respond(false, Status.UnsupportedAction, EmptyObject, "不支持的Action", echo = echo)
|
||||||
|
} else {
|
||||||
|
call.respondText(handler.handle(ActionSession(params, echo)), ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun PipelineContext<Unit, ApplicationCall>.handleAsJsonArray(data: JsonArray) {
|
||||||
|
data.forEach {
|
||||||
|
when (it) {
|
||||||
|
is JsonArray -> handleAsJsonArray(it)
|
||||||
|
is JsonObject -> handleAsJsonObject(it)
|
||||||
|
else -> handleAsJsonObject(it.jsonObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Routing.echoVersion() {
|
fun Routing.echoVersion() {
|
||||||
route("/") {
|
route("/") {
|
||||||
get {
|
get {
|
||||||
@ -49,6 +84,15 @@ fun Routing.echoVersion() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
|
fetchPostJsonElementOrNull()?.let {
|
||||||
|
if (it is JsonArray) {
|
||||||
|
handleAsJsonArray(it)
|
||||||
|
return@post
|
||||||
|
} else if (it is JsonObject) {
|
||||||
|
handleAsJsonObject(it)
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
}
|
||||||
val action = fetchOrThrow("action")
|
val action = fetchOrThrow("action")
|
||||||
val echo = if (isJsonObject("echo") || isJsonArray("echo")) {
|
val echo = if (isJsonObject("echo") || isJsonArray("echo")) {
|
||||||
fetchPostJsonElement("echo")
|
fetchPostJsonElement("echo")
|
||||||
|
@ -29,7 +29,7 @@ import moe.fuqiuluo.shamrock.tools.jsonArray
|
|||||||
import moe.fuqiuluo.shamrock.tools.respond
|
import moe.fuqiuluo.shamrock.tools.respond
|
||||||
|
|
||||||
fun Routing.messageAction() {
|
fun Routing.messageAction() {
|
||||||
route("/send_group_forward_msg") {
|
route("/send_group_forward_(msg|message)".toRegex()) {
|
||||||
post {
|
post {
|
||||||
val groupId = fetchPostOrNull("group_id")
|
val groupId = fetchPostOrNull("group_id")
|
||||||
val messages = fetchPostJsonArray("messages")
|
val messages = fetchPostJsonArray("messages")
|
||||||
@ -40,7 +40,7 @@ fun Routing.messageAction() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
route("/send_private_forward_msg") {
|
route("/send_private_forward_(msg|message)".toRegex()) {
|
||||||
post {
|
post {
|
||||||
val userId = fetchPostOrNull("user_id")
|
val userId = fetchPostOrNull("user_id")
|
||||||
val messages = fetchPostJsonArray("messages")
|
val messages = fetchPostJsonArray("messages")
|
||||||
@ -51,6 +51,18 @@ fun Routing.messageAction() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
route("/send_forward_(msg|message)".toRegex()) {
|
||||||
|
post {
|
||||||
|
val userId = fetchPostOrNull("user_id")
|
||||||
|
val groupId = fetchPostOrNull("group_id")
|
||||||
|
val messages = fetchPostJsonArray("messages")
|
||||||
|
call.respondText(SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId ?: groupId?: "", messages), ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
respond(false, Status.InternalHandlerError, "Not support GET method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getOrPost("/get_forward_msg") {
|
getOrPost("/get_forward_msg") {
|
||||||
val id = fetchOrThrow("id")
|
val id = fetchOrThrow("id")
|
||||||
call.respondText(GetForwardMsg(id), ContentType.Application.Json)
|
call.respondText(GetForwardMsg(id), ContentType.Application.Json)
|
||||||
@ -101,6 +113,7 @@ fun Routing.messageAction() {
|
|||||||
get {
|
get {
|
||||||
val msgType = fetchGetOrThrow("message_type")
|
val msgType = fetchGetOrThrow("message_type")
|
||||||
val message = fetchGetOrThrow("message")
|
val message = fetchGetOrThrow("message")
|
||||||
|
val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3
|
||||||
val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false
|
val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||||
val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType)
|
val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType)
|
||||||
|
|
||||||
@ -112,12 +125,14 @@ fun Routing.messageAction() {
|
|||||||
peerId = if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!,
|
peerId = if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!,
|
||||||
message = message,
|
message = message,
|
||||||
autoEscape = autoEscape,
|
autoEscape = autoEscape,
|
||||||
fromId = groupId ?: userId ?: ""
|
fromId = groupId ?: userId ?: "",
|
||||||
|
retryCnt = retryCnt
|
||||||
), ContentType.Application.Json)
|
), ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
val msgType = fetchPostOrThrow("message_type")
|
val msgType = fetchPostOrThrow("message_type")
|
||||||
val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType)
|
val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType)
|
||||||
|
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3
|
||||||
|
|
||||||
val userId = fetchPostOrNull("user_id")
|
val userId = fetchPostOrNull("user_id")
|
||||||
val groupId = fetchPostOrNull("group_id")
|
val groupId = fetchPostOrNull("group_id")
|
||||||
@ -129,14 +144,16 @@ fun Routing.messageAction() {
|
|||||||
chatType = chatType,
|
chatType = chatType,
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
message = listOf(fetchPostJsonObject("message")).jsonArray,
|
message = listOf(fetchPostJsonObject("message")).jsonArray,
|
||||||
fromId = groupId ?: userId ?: ""
|
fromId = groupId ?: userId ?: "",
|
||||||
|
retryCnt = retryCnt
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
SendMessage(
|
SendMessage(
|
||||||
chatType = chatType,
|
chatType = chatType,
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
message = fetchPostJsonArray("message"),
|
message = fetchPostJsonArray("message"),
|
||||||
fromId = groupId ?: userId ?: ""
|
fromId = groupId ?: userId ?: "",
|
||||||
|
retryCnt = retryCnt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -147,7 +164,8 @@ fun Routing.messageAction() {
|
|||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
message = fetchPostOrThrow("message"),
|
message = fetchPostOrThrow("message"),
|
||||||
autoEscape = autoEscape,
|
autoEscape = autoEscape,
|
||||||
fromId = groupId ?: userId ?: ""
|
fromId = groupId ?: userId ?: "",
|
||||||
|
retryCnt = retryCnt
|
||||||
)
|
)
|
||||||
}, ContentType.Application.Json)
|
}, ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
@ -157,34 +175,38 @@ fun Routing.messageAction() {
|
|||||||
get {
|
get {
|
||||||
val groupId = fetchGetOrThrow("group_id")
|
val groupId = fetchGetOrThrow("group_id")
|
||||||
val message = fetchGetOrThrow("message")
|
val message = fetchGetOrThrow("message")
|
||||||
|
val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3
|
||||||
val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false
|
val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||||
call.respondText(SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, autoEscape))
|
call.respondText(SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, autoEscape, retryCnt = retryCnt))
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
val groupId = fetchPostOrThrow("group_id")
|
val groupId = fetchPostOrThrow("group_id")
|
||||||
|
|
||||||
|
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3
|
||||||
val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false
|
val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||||
|
|
||||||
val result = if (isJsonData()) {
|
val result = if (isJsonData()) {
|
||||||
if (isJsonString("message")) {
|
if (isJsonString("message")) {
|
||||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, fetchPostJsonString("message"), autoEscape)
|
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, fetchPostJsonString("message"), autoEscape, retryCnt = retryCnt)
|
||||||
} else {
|
} else {
|
||||||
if (isJsonObject("message")) {
|
if (isJsonObject("message")) {
|
||||||
SendMessage(
|
SendMessage(
|
||||||
chatType = MsgConstant.KCHATTYPEGROUP,
|
chatType = MsgConstant.KCHATTYPEGROUP,
|
||||||
peerId = groupId,
|
peerId = groupId,
|
||||||
message = listOf(fetchPostJsonObject("message")).jsonArray
|
message = listOf(fetchPostJsonObject("message")).jsonArray,
|
||||||
|
retryCnt = retryCnt
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
SendMessage(
|
SendMessage(
|
||||||
chatType = MsgConstant.KCHATTYPEGROUP,
|
chatType = MsgConstant.KCHATTYPEGROUP,
|
||||||
peerId = groupId,
|
peerId = groupId,
|
||||||
message = fetchPostJsonArray("message")
|
message = fetchPostJsonArray("message"),
|
||||||
|
retryCnt = retryCnt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, fetchPostOrThrow("message"), autoEscape)
|
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, fetchPostOrThrow("message"), autoEscape, retryCnt = retryCnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
call.respondText(result, ContentType.Application.Json)
|
call.respondText(result, ContentType.Application.Json)
|
||||||
@ -196,18 +218,21 @@ fun Routing.messageAction() {
|
|||||||
val userId = fetchGetOrThrow("user_id")
|
val userId = fetchGetOrThrow("user_id")
|
||||||
val groupId = fetchGetOrNull("group_id")
|
val groupId = fetchGetOrNull("group_id")
|
||||||
val message = fetchGetOrThrow("message")
|
val message = fetchGetOrThrow("message")
|
||||||
|
val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3
|
||||||
val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false
|
val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||||
call.respondText(SendMessage(
|
call.respondText(SendMessage(
|
||||||
chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP,
|
chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP,
|
||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = message,
|
message = message,
|
||||||
autoEscape = autoEscape,
|
autoEscape = autoEscape,
|
||||||
fromId = groupId ?: userId
|
fromId = groupId ?: userId,
|
||||||
|
retryCnt = retryCnt
|
||||||
), ContentType.Application.Json)
|
), ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
val userId = fetchPostOrThrow("user_id")
|
val userId = fetchPostOrThrow("user_id")
|
||||||
val groupId = fetchPostOrNull("group_id")
|
val groupId = fetchPostOrNull("group_id")
|
||||||
|
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3
|
||||||
val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false
|
val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||||
|
|
||||||
val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||||
@ -221,7 +246,8 @@ fun Routing.messageAction() {
|
|||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = fetchPostJsonString("message"),
|
message = fetchPostJsonString("message"),
|
||||||
autoEscape = autoEscape,
|
autoEscape = autoEscape,
|
||||||
fromId = fromId
|
fromId = fromId,
|
||||||
|
retryCnt = retryCnt
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if (isJsonObject("message")) {
|
if (isJsonObject("message")) {
|
||||||
@ -229,14 +255,16 @@ fun Routing.messageAction() {
|
|||||||
chatType = chatType,
|
chatType = chatType,
|
||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = listOf(fetchPostJsonObject("message")).jsonArray,
|
message = listOf(fetchPostJsonObject("message")).jsonArray,
|
||||||
fromId = fromId
|
fromId = fromId,
|
||||||
|
retryCnt = retryCnt
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
SendMessage(
|
SendMessage(
|
||||||
chatType = chatType,
|
chatType = chatType,
|
||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = fetchPostJsonArray("message"),
|
message = fetchPostJsonArray("message"),
|
||||||
fromId = fromId
|
fromId = fromId,
|
||||||
|
retryCnt = retryCnt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,7 +274,8 @@ fun Routing.messageAction() {
|
|||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = fetchPostOrThrow("message"),
|
message = fetchPostOrThrow("message"),
|
||||||
autoEscape = autoEscape,
|
autoEscape = autoEscape,
|
||||||
fromId = fromId
|
fromId = fromId,
|
||||||
|
retryCnt = retryCnt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ import moe.fuqiuluo.shamrock.helper.ErrorTokenException
|
|||||||
import io.ktor.server.application.createApplicationPlugin
|
import io.ktor.server.application.createApplicationPlugin
|
||||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
private suspend fun ApplicationCall.checkToken() {
|
private suspend fun ApplicationCall.checkToken() {
|
||||||
val token = ShamrockConfig.getToken()
|
val token = ShamrockConfig.getToken()
|
||||||
@ -12,10 +14,17 @@ private suspend fun ApplicationCall.checkToken() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var accessToken = request.headers["Authorization"]
|
var accessToken = request.headers["Authorization"]
|
||||||
?: fetchOrNull("ticket")
|
?: fetchOrNull("ticket")?.let {
|
||||||
?: fetchOrNull("access_token")
|
URLDecoder.decode(it)
|
||||||
|
}
|
||||||
|
?: fetchOrNull("access_token")?.let {
|
||||||
|
URLDecoder.decode(it)
|
||||||
|
}
|
||||||
|
?: fetchOrNull("token")?.let {
|
||||||
|
URLDecoder.decode(it)
|
||||||
|
}
|
||||||
?: throw ErrorTokenException
|
?: throw ErrorTokenException
|
||||||
if (accessToken.startsWith("Bearer ")) {
|
if (accessToken.startsWith("Bearer ", ignoreCase = true)) {
|
||||||
accessToken = accessToken.substring(7)
|
accessToken = accessToken.substring(7)
|
||||||
}
|
}
|
||||||
if (token != accessToken) {
|
if (token != accessToken) {
|
||||||
|
@ -7,6 +7,9 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
|||||||
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.response.respondText
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -14,6 +17,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
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.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.*
|
import moe.fuqiuluo.qqinterface.servlet.msg.*
|
||||||
import moe.fuqiuluo.shamrock.remote.service.api.HttpTransmitServlet
|
import moe.fuqiuluo.shamrock.remote.service.api.HttpTransmitServlet
|
||||||
@ -21,13 +25,15 @@ import moe.fuqiuluo.shamrock.remote.service.data.push.*
|
|||||||
import moe.fuqiuluo.shamrock.tools.*
|
import moe.fuqiuluo.shamrock.tools.*
|
||||||
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.remote.action.ActionManager
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.handlers.QuickOperation.quicklyReply
|
||||||
|
import moe.fuqiuluo.shamrock.remote.config.ECHO_KEY
|
||||||
|
import moe.fuqiuluo.shamrock.remote.entries.EmptyObject
|
||||||
|
import moe.fuqiuluo.shamrock.remote.entries.Status
|
||||||
import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter
|
import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter
|
||||||
|
|
||||||
internal object HttpService: HttpTransmitServlet() {
|
internal object HttpService: HttpTransmitServlet() {
|
||||||
private val actionMsgTypes = arrayOf(
|
|
||||||
"record", "voice", "video", "markdown"
|
|
||||||
)
|
|
||||||
|
|
||||||
private val jobList = arrayListOf<Job>()
|
private val jobList = arrayListOf<Job>()
|
||||||
|
|
||||||
override fun submitFlowJob(job: Job) {
|
override fun submitFlowJob(job: Job) {
|
||||||
@ -66,99 +72,83 @@ internal object HttpService: HttpTransmitServlet() {
|
|||||||
|
|
||||||
private suspend fun handleQuicklyReply(record: MsgRecord, msgHash: Int, jsonText: String) {
|
private suspend fun handleQuicklyReply(record: MsgRecord, msgHash: Int, jsonText: String) {
|
||||||
try {
|
try {
|
||||||
val data = Json.parseToJsonElement(jsonText).asJsonObject
|
val data = Json.parseToJsonElement(jsonText)
|
||||||
if (data.containsKey("reply")) {
|
|
||||||
LogCenter.log({ "quickly reply successfully" }, Level.DEBUG)
|
if (data is JsonObject) {
|
||||||
val autoEscape = data["auto_escape"].asBooleanOrNull ?: false
|
if (data.containsKey("reply")) {
|
||||||
val atSender = data["at_sender"].asBooleanOrNull ?: false
|
LogCenter.log({ "quickly reply successfully" }, Level.DEBUG)
|
||||||
val autoReply = data["auto_reply"].asBooleanOrNull ?: true
|
val autoEscape = data["auto_escape"].asBooleanOrNull ?: false
|
||||||
val message = data["reply"]
|
val atSender = data["at_sender"].asBooleanOrNull ?: false
|
||||||
if (message is JsonPrimitive) {
|
val autoReply = data["auto_reply"].asBooleanOrNull ?: true
|
||||||
if (autoEscape) {
|
val message = data["reply"]
|
||||||
val msgList = mutableSetOf<JsonElement>()
|
if (message is JsonPrimitive) {
|
||||||
msgList.add(mapOf(
|
if (autoEscape) {
|
||||||
"type" to "text",
|
val msgList = mutableSetOf<JsonElement>()
|
||||||
"data" to mapOf(
|
msgList.add(mapOf(
|
||||||
"text" to message.asString
|
"type" to "text",
|
||||||
|
"data" to mapOf(
|
||||||
|
"text" to message.asString
|
||||||
|
)
|
||||||
|
).json)
|
||||||
|
quicklyReply(
|
||||||
|
record,
|
||||||
|
msgList.jsonArray,
|
||||||
|
msgHash,
|
||||||
|
atSender,
|
||||||
|
autoReply
|
||||||
)
|
)
|
||||||
).json)
|
} else {
|
||||||
|
val messageArray = MessageHelper.decodeCQCode(message.asString)
|
||||||
|
quicklyReply(
|
||||||
|
record,
|
||||||
|
messageArray,
|
||||||
|
msgHash,
|
||||||
|
atSender,
|
||||||
|
autoReply
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (message is JsonArray) {
|
||||||
quicklyReply(
|
quicklyReply(
|
||||||
record,
|
record,
|
||||||
msgList.jsonArray,
|
message,
|
||||||
msgHash,
|
|
||||||
atSender,
|
|
||||||
autoReply
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val messageArray = MessageHelper.decodeCQCode(message.asString)
|
|
||||||
quicklyReply(
|
|
||||||
record,
|
|
||||||
messageArray,
|
|
||||||
msgHash,
|
msgHash,
|
||||||
atSender,
|
atSender,
|
||||||
autoReply
|
autoReply
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (message is JsonArray) {
|
|
||||||
quicklyReply(
|
|
||||||
record,
|
|
||||||
message,
|
|
||||||
msgHash,
|
|
||||||
atSender,
|
|
||||||
autoReply
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("delete") && data["delete"].asBoolean) {
|
||||||
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("delete") && data["delete"].asBoolean) {
|
MsgSvc.recallMsg(msgHash)
|
||||||
MsgSvc.recallMsg(msgHash)
|
}
|
||||||
}
|
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("kick") && data["kick"].asBoolean) {
|
||||||
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("kick") && data["kick"].asBoolean) {
|
GroupSvc.kickMember(record.peerUin, false, record.senderUin)
|
||||||
GroupSvc.kickMember(record.peerUin, false, record.senderUin)
|
}
|
||||||
}
|
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("ban") && data["ban"].asBoolean) {
|
||||||
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("ban") && data["ban"].asBoolean) {
|
val banTime = data["ban_duration"].asIntOrNull ?: (30 * 60)
|
||||||
val banTime = data["ban_duration"].asIntOrNull ?: (30 * 60)
|
if (banTime <= 0) return
|
||||||
if (banTime <= 0) return
|
GroupSvc.banMember(record.peerUin, record.senderUin, banTime)
|
||||||
GroupSvc.banMember(record.peerUin, record.senderUin, banTime)
|
}
|
||||||
|
} else if (data is JsonArray) {
|
||||||
|
data.forEach {
|
||||||
|
handleQuicklyActions(it.asJsonObject)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
LogCenter.log("处理快速操作错误: $e", Level.WARN)
|
LogCenter.log("处理快速操作错误: $e", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun quicklyReply(
|
private suspend fun handleQuicklyActions(data: JsonObject) {
|
||||||
record: MsgRecord,
|
val action = data["action"].asString
|
||||||
message: JsonArray,
|
val echo = data["echo"] ?: EmptyJsonString
|
||||||
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(
|
val params = data["params"].asJsonObjectOrNull ?: EmptyJsonObject
|
||||||
"type" to "reply",
|
|
||||||
"data" to mapOf(
|
val handler = ActionManager[action]
|
||||||
"id" to msgHash
|
if (handler == null) {
|
||||||
)
|
LogCenter.log("HTTP快速操作:不支持的Action: $action", Level.WARN)
|
||||||
).json) // 添加回复
|
} else {
|
||||||
if (MsgConstant.KCHATTYPEGROUP == record.chatType && atSender) {
|
handler.handle(ActionSession(params, echo))
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,6 +18,11 @@ internal class WebSocketClientService(
|
|||||||
) : WebSocketClientServlet(address, heartbeatInterval, wsHeaders) {
|
) : WebSocketClientServlet(address, heartbeatInterval, wsHeaders) {
|
||||||
private val eventJobList = mutableSetOf<Job>()
|
private val eventJobList = mutableSetOf<Job>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
startHeartbeatTimer()
|
||||||
|
initTransmitter()
|
||||||
|
}
|
||||||
|
|
||||||
override fun submitFlowJob(job: Job) {
|
override fun submitFlowJob(job: Job) {
|
||||||
eventJobList.add(job)
|
eventJobList.add(job)
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ internal class WebSocketService(
|
|||||||
.ifNullOrEmpty(handshake.getFieldValue("ticket"))
|
.ifNullOrEmpty(handshake.getFieldValue("ticket"))
|
||||||
.ifNullOrEmpty(handshake.getFieldValue("Authorization"))
|
.ifNullOrEmpty(handshake.getFieldValue("Authorization"))
|
||||||
?: throw ErrorTokenException
|
?: throw ErrorTokenException
|
||||||
if (accessToken.startsWith("Bearer ")) {
|
if (accessToken.startsWith("Bearer ", ignoreCase = true)) {
|
||||||
accessToken = accessToken.substring(7)
|
accessToken = accessToken.substring(7)
|
||||||
}
|
}
|
||||||
val tokenList = token.split(",", "|", ",")
|
val tokenList = token.split(",", "|", ",")
|
||||||
|
@ -25,6 +25,7 @@ import moe.fuqiuluo.shamrock.remote.service.data.push.RequestEvent
|
|||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.RequestSubType
|
import moe.fuqiuluo.shamrock.remote.service.data.push.RequestSubType
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.RequestType
|
import moe.fuqiuluo.shamrock.remote.service.data.push.RequestType
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.Sender
|
import moe.fuqiuluo.shamrock.remote.service.data.push.Sender
|
||||||
|
import moe.fuqiuluo.shamrock.remote.service.data.push.SignDetail
|
||||||
import moe.fuqiuluo.shamrock.tools.ShamrockDsl
|
import moe.fuqiuluo.shamrock.tools.ShamrockDsl
|
||||||
import moe.fuqiuluo.shamrock.tools.json
|
import moe.fuqiuluo.shamrock.tools.json
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
@ -58,7 +59,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
elements: ArrayList<MsgElement>,
|
elements: ArrayList<MsgElement>,
|
||||||
rawMsg: String,
|
rawMsg: String,
|
||||||
msgHash: Int,
|
msgHash: Int,
|
||||||
postType: PostType = PostType.Msg
|
postType: PostType
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val uin = app.longAccountUin
|
val uin = app.longAccountUin
|
||||||
transMessageEvent(record,
|
transMessageEvent(record,
|
||||||
@ -84,7 +85,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
.ifBlank { record.sendRemarkName }
|
.ifBlank { record.sendRemarkName }
|
||||||
.ifBlank { record.sendMemberName }
|
.ifBlank { record.sendMemberName }
|
||||||
.ifBlank { record.peerName },
|
.ifBlank { record.peerName },
|
||||||
card = record.sendMemberName.ifBlank { record.sendNickName },
|
card = record.sendMemberName,
|
||||||
role = when (record.senderUin) {
|
role = when (record.senderUin) {
|
||||||
GroupSvc.getOwner(record.peerUin.toString()) -> MemberRole.Owner
|
GroupSvc.getOwner(record.peerUin.toString()) -> MemberRole.Owner
|
||||||
in GroupSvc.getAdminList(record.peerUin.toString()) -> MemberRole.Admin
|
in GroupSvc.getAdminList(record.peerUin.toString()) -> MemberRole.Admin
|
||||||
@ -106,7 +107,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
elements: ArrayList<MsgElement>,
|
elements: ArrayList<MsgElement>,
|
||||||
rawMsg: String,
|
rawMsg: String,
|
||||||
msgHash: Int,
|
msgHash: Int,
|
||||||
postType: PostType = PostType.Msg,
|
postType: PostType,
|
||||||
tempSource: MessageTempSource = MessageTempSource.Unknown
|
tempSource: MessageTempSource = MessageTempSource.Unknown
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val botUin = app.longAccountUin
|
val botUin = app.longAccountUin
|
||||||
@ -222,6 +223,24 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
* 群聊通知 通知器
|
* 群聊通知 通知器
|
||||||
*/
|
*/
|
||||||
object GroupNoticeTransmitter {
|
object GroupNoticeTransmitter {
|
||||||
|
suspend fun transGroupSign(time: Long, target: Long, action: String?, rankImg: String?, groupCode: Long): Boolean {
|
||||||
|
pushNotice(NoticeEvent(
|
||||||
|
time = time,
|
||||||
|
selfId = app.longAccountUin,
|
||||||
|
postType = PostType.Notice,
|
||||||
|
type = NoticeType.Notify,
|
||||||
|
subType = NoticeSubType.Sign,
|
||||||
|
userId = target,
|
||||||
|
groupId = groupCode,
|
||||||
|
target = target,
|
||||||
|
signDetail = SignDetail(
|
||||||
|
rankImg = rankImg,
|
||||||
|
action = action
|
||||||
|
)
|
||||||
|
))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun transGroupPoke(time: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean {
|
suspend fun transGroupPoke(time: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(NoticeEvent(
|
||||||
time = time,
|
time = time,
|
||||||
@ -245,8 +264,10 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
suspend fun transGroupMemberNumChanged(
|
suspend fun transGroupMemberNumChanged(
|
||||||
time: Long,
|
time: Long,
|
||||||
target: Long,
|
target: Long,
|
||||||
|
targetUid: String,
|
||||||
groupCode: Long,
|
groupCode: Long,
|
||||||
operation: Long,
|
operator: Long,
|
||||||
|
operatorUid: String,
|
||||||
noticeType: NoticeType,
|
noticeType: NoticeType,
|
||||||
noticeSubType: NoticeSubType
|
noticeSubType: NoticeSubType
|
||||||
): Boolean {
|
): Boolean {
|
||||||
@ -256,11 +277,14 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
postType = PostType.Notice,
|
postType = PostType.Notice,
|
||||||
type = noticeType,
|
type = noticeType,
|
||||||
subType = noticeSubType,
|
subType = noticeSubType,
|
||||||
operatorId = operation,
|
operatorId = operator,
|
||||||
userId = target,
|
userId = target,
|
||||||
senderId = operation,
|
senderId = operator,
|
||||||
target = target,
|
target = target,
|
||||||
groupId = groupCode
|
groupId = groupCode,
|
||||||
|
targetUid = targetUid,
|
||||||
|
operatorUid = operatorUid,
|
||||||
|
userUid = targetUid
|
||||||
))
|
))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -268,6 +292,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
suspend fun transGroupAdminChanged(
|
suspend fun transGroupAdminChanged(
|
||||||
msgTime: Long,
|
msgTime: Long,
|
||||||
target: Long,
|
target: Long,
|
||||||
|
targetUid: String,
|
||||||
groupCode: Long,
|
groupCode: Long,
|
||||||
setAdmin: Boolean
|
setAdmin: Boolean
|
||||||
): Boolean {
|
): Boolean {
|
||||||
@ -279,6 +304,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
subType = if (setAdmin) NoticeSubType.Set else NoticeSubType.UnSet,
|
subType = if (setAdmin) NoticeSubType.Set else NoticeSubType.UnSet,
|
||||||
operatorId = 0,
|
operatorId = 0,
|
||||||
target = target,
|
target = target,
|
||||||
|
targetUid = targetUid,
|
||||||
groupId = groupCode
|
groupId = groupCode
|
||||||
))
|
))
|
||||||
return true
|
return true
|
||||||
@ -286,8 +312,11 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
|
|
||||||
suspend fun transGroupBan(
|
suspend fun transGroupBan(
|
||||||
msgTime: Long,
|
msgTime: Long,
|
||||||
operation: Long,
|
subType: NoticeSubType,
|
||||||
|
operator: Long,
|
||||||
|
operatorUid: String,
|
||||||
target: Long,
|
target: Long,
|
||||||
|
targetUid: String,
|
||||||
groupCode: Long,
|
groupCode: Long,
|
||||||
duration: Int
|
duration: Int
|
||||||
): Boolean {
|
): Boolean {
|
||||||
@ -296,13 +325,15 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
selfId = app.longAccountUin,
|
selfId = app.longAccountUin,
|
||||||
postType = PostType.Notice,
|
postType = PostType.Notice,
|
||||||
type = NoticeType.GroupBan,
|
type = NoticeType.GroupBan,
|
||||||
subType = if (duration == 0) NoticeSubType.LiftBan else NoticeSubType.Ban,
|
subType = subType,
|
||||||
operatorId = operation,
|
operatorId = operator,
|
||||||
userId = target,
|
userId = target,
|
||||||
senderId = operation,
|
senderId = operator,
|
||||||
target = target,
|
target = target,
|
||||||
groupId = groupCode,
|
groupId = groupCode,
|
||||||
duration = duration
|
duration = duration,
|
||||||
|
operatorUid = operatorUid,
|
||||||
|
targetUid = targetUid
|
||||||
))
|
))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ internal abstract class HttpTransmitServlet : BaseTransmitServlet {
|
|||||||
if (!allowTransmit()) return null
|
if (!allowTransmit()) return null
|
||||||
try {
|
try {
|
||||||
if (address.startsWith("http://") || address.startsWith("https://")) {
|
if (address.startsWith("http://") || address.startsWith("https://")) {
|
||||||
return GlobalClient.post(address) {
|
val response = GlobalClient.post(address) {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(body)
|
setBody(body)
|
||||||
|
|
||||||
@ -44,6 +44,11 @@ internal abstract class HttpTransmitServlet : BaseTransmitServlet {
|
|||||||
header("X-Client-Role", "Universal")
|
header("X-Client-Role", "Universal")
|
||||||
header("Sec-WebSocket-Protocol", "11.Shamrock")
|
header("Sec-WebSocket-Protocol", "11.Shamrock")
|
||||||
}
|
}
|
||||||
|
return if (response.status.value == 204) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
response
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LogCenter.log("HTTP推送地址错误: ${address}。", Level.ERROR)
|
LogCenter.log("HTTP推送地址错误: ${address}。", Level.ERROR)
|
||||||
}
|
}
|
||||||
|
@ -35,24 +35,22 @@ import java.net.URI
|
|||||||
import kotlin.concurrent.timer
|
import kotlin.concurrent.timer
|
||||||
|
|
||||||
internal abstract class WebSocketClientServlet(
|
internal abstract class WebSocketClientServlet(
|
||||||
url: String,
|
private val url: String,
|
||||||
private val heartbeatInterval: Long,
|
private val heartbeatInterval: Long,
|
||||||
wsHeaders: Map<String, String>
|
private val wsHeaders: Map<String, String>
|
||||||
) : BaseTransmitServlet, WebSocketClient(URI(url), wsHeaders) {
|
) : BaseTransmitServlet, WebSocketClient(URI(url), wsHeaders) {
|
||||||
|
init {
|
||||||
|
if (connectedClients.containsKey(url)) {
|
||||||
|
throw RuntimeException("WebSocketClient已存在: $url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val sendLock = Mutex()
|
private val sendLock = Mutex()
|
||||||
|
|
||||||
override fun allowTransmit(): Boolean {
|
override fun allowTransmit(): Boolean {
|
||||||
return ShamrockConfig.openWebSocketClient()
|
return ShamrockConfig.openWebSocketClient()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpen(handshakedata: ServerHandshake?) {
|
|
||||||
LogCenter.log("WebSocketClient onOpen: ${handshakedata?.httpStatus}, ${handshakedata?.httpStatusMessage}")
|
|
||||||
|
|
||||||
startHeartbeatTimer()
|
|
||||||
pushMetaLifecycle()
|
|
||||||
initTransmitter()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMessage(message: String) {
|
override fun onMessage(message: String) {
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
handleMessage(message)
|
handleMessage(message)
|
||||||
@ -84,14 +82,34 @@ internal abstract class WebSocketClientServlet(
|
|||||||
respond?.let { send(it) }
|
respond?.let { send(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOpen(handshakedata: ServerHandshake?) {
|
||||||
|
LogCenter.log("WebSocketClient onOpen: ${handshakedata?.httpStatus}, ${handshakedata?.httpStatusMessage}")
|
||||||
|
|
||||||
|
connectedClients[url] = this
|
||||||
|
|
||||||
|
//startHeartbeatTimer()
|
||||||
|
pushMetaLifecycle()
|
||||||
|
//initTransmitter()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onClose(code: Int, reason: String?, remote: Boolean) {
|
override fun onClose(code: Int, reason: String?, remote: Boolean) {
|
||||||
|
if (code == 403) {
|
||||||
|
if (wsHeaders.containsKey("authorization")) {
|
||||||
|
val token = wsHeaders["authorization"]!!.substring(7)
|
||||||
|
LogCenter.log("WebSocketClient连接被拒绝, token: $token 失效", Level.WARN)
|
||||||
|
} else {
|
||||||
|
LogCenter.log("WebSocketClient连接被拒绝, 未设置token", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
LogCenter.log("WebSocketClient onClose: $code, $reason, $remote")
|
LogCenter.log("WebSocketClient onClose: $code, $reason, $remote")
|
||||||
cancelFlowJobs()
|
cancelFlowJobs()
|
||||||
|
connectedClients.remove(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(ex: Exception?) {
|
override fun onError(ex: Exception?) {
|
||||||
LogCenter.log("WebSocketClient onError: ${ex?.message}")
|
LogCenter.log("WebSocketClient onError: ${ex?.message}")
|
||||||
cancelFlowJobs()
|
cancelFlowJobs()
|
||||||
|
connectedClients.remove(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected suspend inline fun <reified T> pushTo(body: T) {
|
protected suspend inline fun <reified T> pushTo(body: T) {
|
||||||
@ -105,11 +123,14 @@ internal abstract class WebSocketClientServlet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startHeartbeatTimer() {
|
fun startHeartbeatTimer() {
|
||||||
if (heartbeatInterval <= 0) return
|
if (heartbeatInterval <= 0) {
|
||||||
|
LogCenter.log("被动WebSocket心跳间隔为0,不启动心跳", Level.WARN)
|
||||||
|
return
|
||||||
|
}
|
||||||
timer(
|
timer(
|
||||||
name = "heartbeat",
|
name = "heartbeat",
|
||||||
initialDelay = 0,
|
initialDelay = heartbeatInterval,
|
||||||
period = heartbeatInterval,
|
period = heartbeatInterval,
|
||||||
) {
|
) {
|
||||||
if (isClosed || isClosing || !isOpen) {
|
if (isClosed || isClosing || !isOpen) {
|
||||||
@ -117,6 +138,7 @@ internal abstract class WebSocketClientServlet(
|
|||||||
return@timer
|
return@timer
|
||||||
}
|
}
|
||||||
val runtime = AppRuntimeFetcher.appRuntime
|
val runtime = AppRuntimeFetcher.appRuntime
|
||||||
|
LogCenter.log("WebSocketClient心跳: ${app.longAccountUin}", Level.DEBUG)
|
||||||
send(
|
send(
|
||||||
GlobalJson.encodeToString(
|
GlobalJson.encodeToString(
|
||||||
PushMetaEvent(
|
PushMetaEvent(
|
||||||
@ -131,7 +153,7 @@ internal abstract class WebSocketClientServlet(
|
|||||||
status = "正常",
|
status = "正常",
|
||||||
good = true
|
good = true
|
||||||
),
|
),
|
||||||
interval = 1000L * 15
|
interval = heartbeatInterval
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -157,4 +179,8 @@ internal abstract class WebSocketClientServlet(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val connectedClients = mutableMapOf<String, WebSocketClientServlet>()
|
||||||
|
}
|
||||||
}
|
}
|
@ -61,6 +61,7 @@ internal abstract class WebSocketTransmitServlet(
|
|||||||
timer("heartbeat", true, 0, heartbeatInterval) {
|
timer("heartbeat", true, 0, heartbeatInterval) {
|
||||||
val runtime = AppRuntimeFetcher.appRuntime
|
val runtime = AppRuntimeFetcher.appRuntime
|
||||||
val curUin = runtime.currentAccountUin
|
val curUin = runtime.currentAccountUin
|
||||||
|
LogCenter.log("WebSocket心跳: $curUin", Level.DEBUG)
|
||||||
broadcastAnyEvent(
|
broadcastAnyEvent(
|
||||||
PushMetaEvent(
|
PushMetaEvent(
|
||||||
time = System.currentTimeMillis() / 1000,
|
time = System.currentTimeMillis() / 1000,
|
||||||
@ -78,6 +79,8 @@ internal abstract class WebSocketTransmitServlet(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LogCenter.log("主动WebSocket心跳间隔为0,不启动心跳", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +89,13 @@ internal abstract class WebSocketTransmitServlet(
|
|||||||
if (path != "/api") {
|
if (path != "/api") {
|
||||||
eventReceivers.remove(conn)
|
eventReceivers.remove(conn)
|
||||||
}
|
}
|
||||||
LogCenter.log({ "WSServer断开(${conn.remoteSocketAddress.address.hostAddress}:${conn.remoteSocketAddress.port}$path): $code,$reason,$remote" }, Level.WARN)
|
runCatching {
|
||||||
|
conn.remoteSocketAddress.address.hostAddress to conn.remoteSocketAddress.port
|
||||||
|
}.onSuccess {
|
||||||
|
LogCenter.log({ "WSServer断开(${it.first}:${it.second}$path): $code,$reason,$remote" }, Level.WARN)
|
||||||
|
}.onFailure {
|
||||||
|
LogCenter.log({ "WSServer断开($path): $code,$reason,$remote" }, Level.WARN)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMessage(conn: WebSocket, message: String) {
|
override fun onMessage(conn: WebSocket, message: String) {
|
||||||
|
@ -76,6 +76,7 @@ internal object ShamrockConfig {
|
|||||||
|
|
||||||
putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息
|
putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息
|
||||||
putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口
|
putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口
|
||||||
|
putBoolean("enable_sync_msg_as_sent_msg", intent.getBooleanExtra("enable_sync_msg_as_sent_msg", false)) // 推送同步消息
|
||||||
|
|
||||||
putBoolean("isInit", true)
|
putBoolean("isInit", true)
|
||||||
}
|
}
|
||||||
@ -101,6 +102,10 @@ internal object ShamrockConfig {
|
|||||||
return Config.rules?.privateRule
|
return Config.rules?.privateRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun enableSyncMsgAsSentMsg(): Boolean {
|
||||||
|
return mmkv.getBoolean("enable_sync_msg_as_sent_msg", false)
|
||||||
|
}
|
||||||
|
|
||||||
fun enableSelfMsg(): Boolean {
|
fun enableSelfMsg(): Boolean {
|
||||||
return mmkv.getBoolean("enable_self_msg", false)
|
return mmkv.getBoolean("enable_self_msg", false)
|
||||||
}
|
}
|
||||||
|
@ -16,3 +16,22 @@ internal data class FriendEntry(
|
|||||||
@SerialName("term_type") val termType: Int,
|
@SerialName("term_type") val termType: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
internal data class FriendRequest(
|
||||||
|
@SerialName("request_id") val seq: Long = 0,
|
||||||
|
@SerialName("requester_uin") val userId: Long = 0,
|
||||||
|
@SerialName("requester_nick") val name: String?,
|
||||||
|
val source: String?,
|
||||||
|
@SerialName("sub_id") val subId: Int?,
|
||||||
|
@SerialName("sub_src_id") val subSrcId: Int?,
|
||||||
|
@SerialName("message") val msg: String?,
|
||||||
|
@SerialName("source_group_name") val sourceGroupName: String?,
|
||||||
|
@SerialName("source_group_id") val sourceGroupCode: Long?,
|
||||||
|
val flag: String,
|
||||||
|
val sex: String?,
|
||||||
|
val age: Int?,
|
||||||
|
@SerialName("msg_detail") val msgDetail: String?,
|
||||||
|
val status: String?,
|
||||||
|
|
||||||
|
)
|
@ -26,6 +26,7 @@ internal data class SimpleTroopMemberInfo(
|
|||||||
@SerialName("group_id") val groupId: Long,
|
@SerialName("group_id") val groupId: Long,
|
||||||
@SerialName("user_name") val name: String,
|
@SerialName("user_name") val name: String,
|
||||||
@SerialName("sex") val sex: String,
|
@SerialName("sex") val sex: String,
|
||||||
|
@SerialName("age") val age: Int,
|
||||||
@SerialName("title") val title: String,
|
@SerialName("title") val title: String,
|
||||||
@SerialName("title_expire_time") val titleExpireTime: Int,
|
@SerialName("title_expire_time") val titleExpireTime: Int,
|
||||||
@SerialName("nickname") val nick: String,
|
@SerialName("nickname") val nick: String,
|
||||||
@ -42,6 +43,7 @@ internal data class SimpleTroopMemberInfo(
|
|||||||
@SerialName("role") val role: MemberRole,
|
@SerialName("role") val role: MemberRole,
|
||||||
@SerialName("unfriendly") val unfriendly: Boolean,
|
@SerialName("unfriendly") val unfriendly: Boolean,
|
||||||
@SerialName("card_changeable") val cardChangeable: Boolean,
|
@SerialName("card_changeable") val cardChangeable: Boolean,
|
||||||
|
@SerialName("shut_up_timestamp") val shutUpTimestamp: Long?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -20,7 +20,7 @@ internal enum class NoticeType {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal enum class RequestType {
|
internal enum class RequestType {
|
||||||
@SerialName("friend ") Friend,
|
@SerialName("friend") Friend,
|
||||||
@SerialName("group") Group,
|
@SerialName("group") Group,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +42,8 @@ internal enum class NoticeSubType {
|
|||||||
@SerialName("kick_me") KickMe,
|
@SerialName("kick_me") KickMe,
|
||||||
|
|
||||||
@SerialName("poke") Poke,
|
@SerialName("poke") Poke,
|
||||||
|
@SerialName("sign") Sign,
|
||||||
|
|
||||||
|
|
||||||
@SerialName("title") Title,
|
@SerialName("title") Title,
|
||||||
@SerialName("delete") Delete,
|
@SerialName("delete") Delete,
|
||||||
@ -65,14 +67,20 @@ internal data class NoticeEvent(
|
|||||||
@SerialName("post_type") val postType: PostType,
|
@SerialName("post_type") val postType: PostType,
|
||||||
@SerialName("notice_type") val type: NoticeType,
|
@SerialName("notice_type") val type: NoticeType,
|
||||||
@SerialName("sub_type") val subType: NoticeSubType = NoticeSubType.None,
|
@SerialName("sub_type") val subType: NoticeSubType = NoticeSubType.None,
|
||||||
@SerialName("group_id") val groupId: Long = 0,
|
@SerialName("group_id") val groupId: Long = Long.MIN_VALUE,
|
||||||
@SerialName("operator_id") val operatorId: Long = 0,
|
@SerialName("operator_id") val operatorId: Long = Long.MIN_VALUE,
|
||||||
@SerialName("user_id") val userId: Long = 0,
|
@SerialName("operator_uid") val operatorUid: String = "",
|
||||||
@SerialName("sender_id") val senderId: Long = 0,
|
@SerialName("user_id") val userId: Long = Long.MIN_VALUE,
|
||||||
@SerialName("duration") val duration: Int = 0,
|
@SerialName("user_uid") val userUid: String = "",
|
||||||
@SerialName("message_id") val msgId: Int = 0,
|
@SerialName("sender_id") val senderId: Long = Long.MIN_VALUE,
|
||||||
|
@SerialName("duration") val duration: Int = Int.MIN_VALUE,
|
||||||
|
@SerialName("message_id") val msgId: Int = Int.MIN_VALUE,
|
||||||
@SerialName("tip_text") val tip: String = "",
|
@SerialName("tip_text") val tip: String = "",
|
||||||
@SerialName("target_id") val target: Long = 0,
|
|
||||||
|
@SerialName("target_id") val target: Long = Long.MIN_VALUE,
|
||||||
|
@SerialName("target_uid") val targetUid: String = "",
|
||||||
|
|
||||||
|
|
||||||
@SerialName("file") val file: GroupFileMsg? = null,
|
@SerialName("file") val file: GroupFileMsg? = null,
|
||||||
@SerialName("private_file") val privateFile: PrivateFileMsg? = null,
|
@SerialName("private_file") val privateFile: PrivateFileMsg? = null,
|
||||||
@SerialName("flag") val flag: String? = null,
|
@SerialName("flag") val flag: String? = null,
|
||||||
@ -87,7 +95,10 @@ internal data class NoticeEvent(
|
|||||||
// 戳一戳
|
// 戳一戳
|
||||||
@SerialName("poke_detail") val pokeDetail: PokeDetail? = null,
|
@SerialName("poke_detail") val pokeDetail: PokeDetail? = null,
|
||||||
|
|
||||||
)
|
// 群打卡
|
||||||
|
@SerialName("sign_detail") val signDetail: SignDetail? = null,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不要使用继承的方式实现通用字段,那样会很难维护!
|
* 不要使用继承的方式实现通用字段,那样会很难维护!
|
||||||
@ -99,8 +110,8 @@ internal data class RequestEvent(
|
|||||||
@SerialName("post_type") val postType: PostType,
|
@SerialName("post_type") val postType: PostType,
|
||||||
@SerialName("request_type") val type: RequestType,
|
@SerialName("request_type") val type: RequestType,
|
||||||
@SerialName("sub_type") val subType: RequestSubType = RequestSubType.None,
|
@SerialName("sub_type") val subType: RequestSubType = RequestSubType.None,
|
||||||
@SerialName("group_id") val groupId: Long = 0,
|
@SerialName("group_id") val groupId: Long = -1,
|
||||||
@SerialName("user_id") val userId: Long = 0,
|
@SerialName("user_id") val userId: Long = -1,
|
||||||
@SerialName("comment") val comment: String = "",
|
@SerialName("comment") val comment: String = "",
|
||||||
@SerialName("flag") val flag: String? = null,
|
@SerialName("flag") val flag: String? = null,
|
||||||
)
|
)
|
||||||
@ -131,4 +142,11 @@ internal data class PokeDetail (
|
|||||||
val suffix: String? = "",
|
val suffix: String? = "",
|
||||||
@SerialName("action_img_url")
|
@SerialName("action_img_url")
|
||||||
val actionImg: String? = "https://tianquan.gtimg.cn/nudgeaction/item/0/expression.jpg",
|
val actionImg: String? = "https://tianquan.gtimg.cn/nudgeaction/item/0/expression.jpg",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
internal data class SignDetail (
|
||||||
|
val action: String? = "今日第1个打卡",
|
||||||
|
@SerialName("rank_img")
|
||||||
|
val rankImg: String? = "",
|
||||||
|
)
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
@file:OptIn(DelicateCoroutinesApi::class)
|
@file:OptIn(DelicateCoroutinesApi::class)
|
||||||
|
|
||||||
package moe.fuqiuluo.shamrock.remote.service.listener
|
package moe.fuqiuluo.shamrock.remote.service.listener
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.*
|
import com.tencent.qqnt.kernel.nativeinterface.*
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toCQCode
|
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toCQCode
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
|
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
|
||||||
@ -19,12 +18,11 @@ import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter
|
|||||||
import moe.fuqiuluo.shamrock.remote.service.api.RichMediaUploadHandler
|
import moe.fuqiuluo.shamrock.remote.service.api.RichMediaUploadHandler
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource
|
import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.PostType
|
import moe.fuqiuluo.shamrock.remote.service.data.push.PostType
|
||||||
import mqq.app.MobileQQ
|
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
internal object AioListener: IKernelMsgListener {
|
internal object AioListener : IKernelMsgListener {
|
||||||
// 通过MSG SEQ临时监听器
|
// 通过MSG SEQ临时监听器
|
||||||
internal val messageLessListenerMap = Collections.synchronizedMap(HashMap<Long, MsgRecord.() -> Unit>())
|
internal val messageLessListenerMap = Collections.synchronizedMap(HashMap<Long, MsgRecord.() -> Unit>())
|
||||||
|
|
||||||
@ -43,13 +41,12 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return // TODO: 频道消息暂不处理
|
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return // TODO: 频道消息暂不处理
|
||||||
|
|
||||||
messageLessListenerMap.firstNotNullOfOrNull {
|
messageLessListenerMap.firstNotNullOfOrNull {
|
||||||
if(it.key == record.msgSeq) it else null
|
if (it.key == record.msgSeq) it else null
|
||||||
}?.let {
|
}?.let {
|
||||||
it.value(record)
|
it.value(record)
|
||||||
messageLessListenerMap.remove(it.key)
|
messageLessListenerMap.remove(it.key)
|
||||||
}
|
}
|
||||||
|
if (record.msgSeq < 0) return
|
||||||
if (record.msgSeq < 0) return
|
|
||||||
|
|
||||||
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
|
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
|
||||||
|
|
||||||
@ -67,9 +64,14 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
if (rawMsg.isEmpty()) return
|
if (rawMsg.isEmpty()) return
|
||||||
|
|
||||||
if (ShamrockConfig.aliveReply() && rawMsg == "ping") {
|
if (ShamrockConfig.aliveReply() && rawMsg == "ping") {
|
||||||
MessageHelper.sendMessageWithoutMsgId(record.chatType, record.peerUin.toString(), "pong", { _, _ -> })
|
MessageHelper.sendMessageWithoutMsgId(record.chatType, record.peerUin.toString(), "pong", { _, _ -> })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val postType = if (record.senderUin == TicketSvc.getLongUin() && ShamrockConfig.enableSyncMsgAsSentMsg()) {
|
||||||
|
PostType.MsgSent
|
||||||
|
} else PostType.Msg
|
||||||
|
|
||||||
//if (rawMsg.contains("forward")) {
|
//if (rawMsg.contains("forward")) {
|
||||||
// LogCenter.log(record.extInfoForUI.decodeToString(), Level.WARN)
|
// LogCenter.log(record.extInfoForUI.decodeToString(), Level.WARN)
|
||||||
//}
|
//}
|
||||||
@ -83,7 +85,7 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!GlobalEventTransmitter.MessageTransmitter.transGroupMessage(
|
if(!GlobalEventTransmitter.MessageTransmitter.transGroupMessage(
|
||||||
record, record.elements, rawMsg, msgHash
|
record, record.elements, rawMsg, msgHash, postType
|
||||||
)) {
|
)) {
|
||||||
LogCenter.log("群消息推送失败 -> 推送目标可能不存在", Level.WARN)
|
LogCenter.log("群消息推送失败 -> 推送目标可能不存在", Level.WARN)
|
||||||
}
|
}
|
||||||
@ -96,7 +98,7 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage(
|
if(!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage(
|
||||||
record, record.elements, rawMsg, msgHash
|
record, record.elements, rawMsg, msgHash, postType
|
||||||
)) {
|
)) {
|
||||||
LogCenter.log("私聊消息推送失败 -> MessageTransmitter", Level.WARN)
|
LogCenter.log("私聊消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||||
}
|
}
|
||||||
@ -112,8 +114,8 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage(
|
if(!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage(
|
||||||
record, record.elements, rawMsg, msgHash, tempSource = MessageTempSource.Group
|
record, record.elements, rawMsg, msgHash, tempSource = MessageTempSource.Group, postType = postType
|
||||||
)) {
|
)) {
|
||||||
LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
|
LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,7 +160,8 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return@forEach// TODO: 频道消息暂不处理
|
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return@forEach// TODO: 频道消息暂不处理
|
||||||
|
|
||||||
if (record.sendStatus == MsgConstant.KSENDSTATUSFAILED
|
if (record.sendStatus == MsgConstant.KSENDSTATUSFAILED
|
||||||
|| record.sendStatus == MsgConstant.KSENDSTATUSSENDING) {
|
|| record.sendStatus == MsgConstant.KSENDSTATUSSENDING
|
||||||
|
) {
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,8 +185,10 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
.updateMsgSeqByMsgHash(msgHash, record.msgSeq.toInt())
|
.updateMsgSeqByMsgHash(msgHash, record.msgSeq.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ShamrockConfig.enableSelfMsg() || record.senderUin != TicketSvc.getLongUin())
|
if (!ShamrockConfig.enableSelfMsg()
|
||||||
return@launch
|
|| record.senderUin != TicketSvc.getLongUin()
|
||||||
|
|| record.peerUin == TicketSvc.getLongUin()
|
||||||
|
) return@launch
|
||||||
|
|
||||||
val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString())
|
val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString())
|
||||||
if (rawMsg.isEmpty()) return@launch
|
if (rawMsg.isEmpty()) return@launch
|
||||||
@ -191,24 +196,37 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
|
|
||||||
when (record.chatType) {
|
when (record.chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> {
|
MsgConstant.KCHATTYPEGROUP -> {
|
||||||
if(!GlobalEventTransmitter.MessageTransmitter
|
if (!GlobalEventTransmitter.MessageTransmitter
|
||||||
.transGroupMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent)) {
|
.transGroupMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent)
|
||||||
|
) {
|
||||||
LogCenter.log("自发群消息推送失败 -> MessageTransmitter", Level.WARN)
|
LogCenter.log("自发群消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MsgConstant.KCHATTYPEC2C -> {
|
MsgConstant.KCHATTYPEC2C -> {
|
||||||
if(!GlobalEventTransmitter.MessageTransmitter
|
if (!GlobalEventTransmitter.MessageTransmitter
|
||||||
.transPrivateMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent)) {
|
.transPrivateMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent)
|
||||||
|
) {
|
||||||
LogCenter.log("自发私聊消息推送失败 -> MessageTransmitter", Level.WARN)
|
LogCenter.log("自发私聊消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||||
if (!ShamrockConfig.allowTempSession()) return@launch
|
if (!ShamrockConfig.allowTempSession()) return@launch
|
||||||
if(!GlobalEventTransmitter.MessageTransmitter
|
if (!GlobalEventTransmitter.MessageTransmitter
|
||||||
.transPrivateMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent, MessageTempSource.Group)) {
|
.transPrivateMessage(
|
||||||
|
record,
|
||||||
|
record.elements,
|
||||||
|
rawMsg,
|
||||||
|
msgHash,
|
||||||
|
PostType.MsgSent,
|
||||||
|
MessageTempSource.Group
|
||||||
|
)
|
||||||
|
) {
|
||||||
LogCenter.log("自发私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
|
LogCenter.log("自发私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> LogCenter.log("不支持SELF PUSH事件: ${record.chatType}")
|
else -> LogCenter.log("不支持SELF PUSH事件: ${record.chatType}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,7 +323,7 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
override fun onFileMsgCome(arrayList: ArrayList<MsgRecord>?) {
|
override fun onFileMsgCome(arrayList: ArrayList<MsgRecord>?) {
|
||||||
arrayList?.forEach { record ->
|
arrayList?.forEach { record ->
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
when(record.chatType) {
|
when (record.chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> onGroupFileMsg(record)
|
MsgConstant.KCHATTYPEGROUP -> onGroupFileMsg(record)
|
||||||
MsgConstant.KCHATTYPEC2C -> onC2CFileMsg(record)
|
MsgConstant.KCHATTYPEC2C -> onC2CFileMsg(record)
|
||||||
else -> LogCenter.log("不支持该来源的文件上传事件:${record}", Level.WARN)
|
else -> LogCenter.log("不支持该来源的文件上传事件:${record}", Level.WARN)
|
||||||
@ -330,8 +348,9 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
val fileSubId = fileMsg.fileSubId ?: ""
|
val fileSubId = fileMsg.fileSubId ?: ""
|
||||||
val url = RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
|
val url = RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
|
||||||
|
|
||||||
if(!GlobalEventTransmitter.FileNoticeTransmitter
|
if (!GlobalEventTransmitter.FileNoticeTransmitter
|
||||||
.transPrivateFileEvent(record.msgTime, userId, fileId, fileSubId, fileName, fileSize, expireTime, url)) {
|
.transPrivateFileEvent(record.msgTime, userId, fileId, fileSubId, fileName, fileSize, expireTime, url)
|
||||||
|
) {
|
||||||
LogCenter.log("私聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
|
LogCenter.log("私聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,8 +372,9 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
|
|
||||||
val url = RichProtoSvc.getGroupFileDownUrl(record.peerUin, uuid, bizId)
|
val url = RichProtoSvc.getGroupFileDownUrl(record.peerUin, uuid, bizId)
|
||||||
|
|
||||||
if(!GlobalEventTransmitter.FileNoticeTransmitter
|
if (!GlobalEventTransmitter.FileNoticeTransmitter
|
||||||
.transGroupFileEvent(record.msgTime, userId, groupId, uuid, fileName, fileSize, bizId, url)) {
|
.transGroupFileEvent(record.msgTime, userId, groupId, uuid, fileName, fileSize, bizId, url)
|
||||||
|
) {
|
||||||
LogCenter.log("群聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
|
LogCenter.log("群聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,17 +12,11 @@ import kotlinx.io.core.discardExact
|
|||||||
import kotlinx.io.core.readBytes
|
import kotlinx.io.core.readBytes
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import moe.fuqiuluo.proto.ProtoByteString
|
import moe.fuqiuluo.proto.*
|
||||||
import moe.fuqiuluo.proto.ProtoMap
|
|
||||||
import moe.fuqiuluo.proto.asInt
|
|
||||||
import moe.fuqiuluo.proto.asLong
|
|
||||||
import moe.fuqiuluo.proto.asUtf8String
|
|
||||||
import moe.fuqiuluo.proto.ProtoUtils
|
|
||||||
import moe.fuqiuluo.proto.asByteArray
|
|
||||||
import moe.fuqiuluo.proto.asList
|
|
||||||
import moe.fuqiuluo.proto.asULong
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.FriendSvc.requestFriendSystemMsgNew
|
import moe.fuqiuluo.qqinterface.servlet.FriendSvc.requestFriendSystemMsgNew
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.GroupSvc.requestGroupSystemMsgNew
|
import moe.fuqiuluo.qqinterface.servlet.GroupSvc.requestGroupSystemMsgNew
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getLongUin
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeSubType
|
import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeSubType
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeType
|
import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeType
|
||||||
@ -63,28 +57,32 @@ internal object PrimitiveListener {
|
|||||||
subType = pb[1, 2, 2].asInt
|
subType = pb[1, 2, 2].asInt
|
||||||
}
|
}
|
||||||
val msgTime = pb[1, 2, 6].asLong
|
val msgTime = pb[1, 2, 6].asLong
|
||||||
when (msgType) {
|
try {
|
||||||
33 -> onGroupMemIncreased(msgTime, pb)
|
when (msgType) {
|
||||||
34 -> onGroupMemberDecreased(msgTime, pb)
|
33 -> onGroupMemIncreased(msgTime, pb)
|
||||||
44 -> onGroupAdminChange(msgTime, pb)
|
34 -> onGroupMemberDecreased(msgTime, pb)
|
||||||
84 -> onGroupApply(msgTime, pb)
|
44 -> onGroupAdminChange(msgTime, pb)
|
||||||
87 -> onInviteGroup(msgTime, pb)
|
84 -> onGroupApply(msgTime, pb)
|
||||||
528 -> when (subType) {
|
87 -> onInviteGroup(msgTime, pb)
|
||||||
35 -> onFriendApply(msgTime, pb)
|
528 -> when (subType) {
|
||||||
39 -> onCardChange(msgTime, pb)
|
35 -> onFriendApply(msgTime, pb)
|
||||||
// invite
|
39 -> onCardChange(msgTime, pb)
|
||||||
68 -> onGroupApply(msgTime, pb)
|
// invite
|
||||||
138 -> onC2CRecall(msgTime, pb)
|
68 -> onGroupApply(msgTime, pb)
|
||||||
290 -> onC2cPoke(msgTime, pb)
|
138 -> onC2CRecall(msgTime, pb)
|
||||||
}
|
290 -> onC2cPoke(msgTime, pb)
|
||||||
|
}
|
||||||
|
|
||||||
732 -> when (subType) {
|
732 -> when (subType) {
|
||||||
12 -> onGroupBan(msgTime, pb)
|
12 -> onGroupBan(msgTime, pb)
|
||||||
16 -> onGroupTitleChange(msgTime, pb)
|
16 -> onGroupTitleChange(msgTime, pb)
|
||||||
17 -> onGroupRecall(msgTime, pb)
|
17 -> onGroupRecall(msgTime, pb)
|
||||||
20 -> onGroupPoke(msgTime, pb)
|
20 -> onGroupPokeAndGroupSign(msgTime, pb)
|
||||||
21 -> onEssenceMessage(msgTime, pb)
|
21 -> onEssenceMessage(msgTime, pb)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogCenter.log("onMsgPush(msgType: $msgType, subType: $subType): "+e.stackTraceToString(), Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,8 +151,21 @@ internal object PrimitiveListener {
|
|||||||
|
|
||||||
|
|
||||||
private suspend fun onCardChange(msgTime: Long, pb: ProtoMap) {
|
private suspend fun onCardChange(msgTime: Long, pb: ProtoMap) {
|
||||||
val targetId = pb[1, 3, 2, 1, 13, 2].asUtf8String
|
var detail = pb[1, 3, 2]
|
||||||
val newCardList = pb[1, 3, 2, 1, 13, 3].asList
|
if (detail !is ProtoMap) {
|
||||||
|
try {
|
||||||
|
val readPacket = ByteReadPacket(detail.asByteArray)
|
||||||
|
readPacket.readBuf32Long()
|
||||||
|
readPacket.discardExact(1)
|
||||||
|
detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
||||||
|
readPacket.release()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogCenter.log("onCardChange error: ${e.stackTraceToString()}", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val targetId = detail[1, 13, 2].asUtf8String
|
||||||
|
val newCardList = detail[1, 13, 3].asList
|
||||||
var newCard = ""
|
var newCard = ""
|
||||||
newCardList
|
newCardList
|
||||||
.value
|
.value
|
||||||
@ -163,7 +174,7 @@ internal object PrimitiveListener {
|
|||||||
newCard = it[2].asUtf8String
|
newCard = it[2].asUtf8String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val groupId = pb[1, 3, 2, 1, 13, 4].asLong
|
val groupId = detail[1, 13, 4].asLong
|
||||||
var oldCard = ""
|
var oldCard = ""
|
||||||
val targetQQ = ContactHelper.getUinByUidAsync(targetId).toLong()
|
val targetQQ = ContactHelper.getUinByUidAsync(targetId).toLong()
|
||||||
LogCenter.log("群组[$groupId]成员$targetQQ 群名片变动 -> $newCard")
|
LogCenter.log("群组[$groupId]成员$targetQQ 群名片变动 -> $newCard")
|
||||||
@ -181,20 +192,35 @@ internal object PrimitiveListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onGroupTitleChange(msgTime: Long, pb: ProtoMap) {
|
private suspend fun onGroupTitleChange(msgTime: Long, pb: ProtoMap) {
|
||||||
val groupCode = pb[1, 1, 1].asULong
|
var detail = pb[1, 3, 2]
|
||||||
|
if (detail !is ProtoMap) {
|
||||||
|
try {
|
||||||
|
val readPacket = ByteReadPacket(detail.asByteArray)
|
||||||
|
readPacket.readBuf32Long()
|
||||||
|
readPacket.discardExact(1)
|
||||||
|
detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
||||||
|
readPacket.release()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogCenter.log("onGroupTitleChange error: ${e.stackTraceToString()}", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var groupId:Long
|
||||||
|
try {
|
||||||
|
groupId = detail[4].asULong
|
||||||
|
}catch (e: ClassCastException){
|
||||||
|
groupId = detail[4].asList.value[0].asULong
|
||||||
|
}
|
||||||
|
|
||||||
val readPacket = ByteReadPacket(pb[1, 3, 2].asByteArray)
|
detail = if (detail[5] is ProtoList) {
|
||||||
val detail = if (readPacket.readBuf32Long() == groupCode) {
|
(detail[5] as ProtoList).value[0]
|
||||||
readPacket.discardExact(1)
|
} else {
|
||||||
ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
detail[5]
|
||||||
} else pb[1, 3, 2]
|
}
|
||||||
|
|
||||||
val targetUin = detail[5, 5].asLong
|
val targetUin = detail[5].asLong
|
||||||
|
|
||||||
val groupId = detail[4].asLong
|
|
||||||
|
|
||||||
// 恭喜<{\"cmd\":5,\"data\":\"qq\",\"text}\":\"nickname\"}>获得群主授予的<{\"cmd\":1,\"data\":\"https://qun.qq.com/qqweb/m/qun/medal/detail.html?_wv=16777223&bid=2504&gc=gid&isnew=1&medal=302&uin=uin\",\"text\":\"title\",\"url\":\"https://qun.qq.com/qqweb/m/qun/medal/detail.html?_wv=16777223&bid=2504&gc=gid&isnew=1&medal=302&uin=uin\"}>头衔
|
// 恭喜<{\"cmd\":5,\"data\":\"qq\",\"text}\":\"nickname\"}>获得群主授予的<{\"cmd\":1,\"data\":\"https://qun.qq.com/qqweb/m/qun/medal/detail.html?_wv=16777223&bid=2504&gc=gid&isnew=1&medal=302&uin=uin\",\"text\":\"title\",\"url\":\"https://qun.qq.com/qqweb/m/qun/medal/detail.html?_wv=16777223&bid=2504&gc=gid&isnew=1&medal=302&uin=uin\"}>头衔
|
||||||
val titleChangeInfo = detail[5, 2].asUtf8String
|
val titleChangeInfo = detail[2].asUtf8String
|
||||||
if (titleChangeInfo.indexOf("群主授予") == -1) {
|
if (titleChangeInfo.indexOf("群主授予") == -1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -212,15 +238,25 @@ internal object PrimitiveListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onEssenceMessage(msgTime: Long, pb: ProtoMap) {
|
private suspend fun onEssenceMessage(msgTime: Long, pb: ProtoMap) {
|
||||||
val groupCode = pb[1, 1, 1].asULong
|
var detail = pb[1, 3, 2]
|
||||||
|
if (detail !is ProtoMap) {
|
||||||
|
try {
|
||||||
|
val readPacket = ByteReadPacket(detail.asByteArray)
|
||||||
|
readPacket.readBuf32Long()
|
||||||
|
readPacket.discardExact(1)
|
||||||
|
detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
||||||
|
readPacket.release()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogCenter.log("onEssenceMessage error: ${e.stackTraceToString()}", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val readPacket = ByteReadPacket(pb[1, 3, 2].asByteArray)
|
var groupId:Long
|
||||||
val detail = if (readPacket.readBuf32Long() == groupCode) {
|
try {
|
||||||
readPacket.discardExact(1)
|
groupId = detail[4].asULong
|
||||||
ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
}catch (e: ClassCastException){
|
||||||
} else pb[1, 3, 2]
|
groupId = detail[4].asList.value[0].asULong
|
||||||
|
}
|
||||||
val groupId = detail[4].asLong
|
|
||||||
val mesSeq = detail[37].asInt
|
val mesSeq = detail[37].asInt
|
||||||
val senderUin = detail[33, 5].asLong
|
val senderUin = detail[33, 5].asLong
|
||||||
val operatorUin = detail[33, 6].asLong
|
val operatorUin = detail[33, 6].asLong
|
||||||
@ -254,32 +290,38 @@ internal object PrimitiveListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private suspend fun onGroupPoke(time: Long, pb: ProtoMap) {
|
private suspend fun onGroupPokeAndGroupSign(time: Long, pb: ProtoMap) {
|
||||||
val groupCode1 = pb[1, 1, 1].asULong
|
var detail = pb[1, 3, 2]
|
||||||
|
|
||||||
var groupCode: Long = groupCode1
|
|
||||||
|
|
||||||
val readPacket = ByteReadPacket(pb[1, 3, 2].asByteArray)
|
|
||||||
val groupCode2 = readPacket.readBuf32Long()
|
|
||||||
|
|
||||||
var detail = if (groupCode2 == groupCode1) {
|
|
||||||
groupCode = groupCode2
|
|
||||||
readPacket.discardExact(1)
|
|
||||||
ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
|
||||||
} else pb[1, 3, 2]
|
|
||||||
if (detail !is ProtoMap) {
|
if (detail !is ProtoMap) {
|
||||||
groupCode = groupCode2
|
try {
|
||||||
readPacket.discardExact(1)
|
val readPacket = ByteReadPacket(detail.asByteArray)
|
||||||
detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
readPacket.discardExact(4)
|
||||||
|
readPacket.discardExact(1)
|
||||||
|
detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
||||||
|
readPacket.release()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogCenter.log("onGroupPokeAndGroupSign error: ${e.stackTraceToString()}", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val groupId = try {
|
||||||
|
detail[4].asULong
|
||||||
|
}catch (e: ClassCastException){
|
||||||
|
detail[4].asList.value[0].asULong
|
||||||
|
}
|
||||||
|
|
||||||
|
detail = if (detail[26] is ProtoList) {
|
||||||
|
(detail[26] as ProtoList).value[0]
|
||||||
|
} else {
|
||||||
|
detail[26]
|
||||||
}
|
}
|
||||||
readPacket.release()
|
|
||||||
|
|
||||||
lateinit var target: String
|
lateinit var target: String
|
||||||
lateinit var operation: String
|
lateinit var operation: String
|
||||||
var action: String? = null
|
var action: String? = null
|
||||||
var suffix: String? = null
|
var suffix: String? = null
|
||||||
var actionImg: String? = null
|
var actionImg: String? = null
|
||||||
detail[26][7]
|
var rankImg: String? = null
|
||||||
|
detail[7]
|
||||||
.asList
|
.asList
|
||||||
.value
|
.value
|
||||||
.forEach {
|
.forEach {
|
||||||
@ -287,18 +329,42 @@ internal object PrimitiveListener {
|
|||||||
when (it[1].asUtf8String) {
|
when (it[1].asUtf8String) {
|
||||||
"uin_str1" -> operation = value
|
"uin_str1" -> operation = value
|
||||||
"uin_str2" -> target = value
|
"uin_str2" -> target = value
|
||||||
|
// "nick_str1" -> operation_nick = value
|
||||||
|
// "nick_str2" -> operation_nick = value
|
||||||
"action_str" -> action = value
|
"action_str" -> action = value
|
||||||
"alt_str1" -> action = value
|
"alt_str1" -> action = value
|
||||||
"suffix_str" -> suffix = value
|
"suffix_str" -> suffix = value
|
||||||
"action_img_url" -> actionImg = value
|
"action_img_url" -> actionImg = value
|
||||||
|
|
||||||
|
"mqq_uin" -> target = value
|
||||||
|
// "mqq_nick" -> operation_nick = value
|
||||||
|
"user_sign" -> action = value
|
||||||
|
"rank_img" -> rankImg = value
|
||||||
|
// "sign_word" -> 我也要打卡
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (detail[2].asInt) {
|
||||||
|
1061 -> {
|
||||||
|
LogCenter.log("群戳一戳($groupId): $operation $action $target $suffix")
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupId)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群戳一戳推送失败!", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LogCenter.log("群戳一戳($groupCode): $operation $action $target $suffix")
|
|
||||||
|
|
||||||
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
1068 -> {
|
||||||
.transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupCode)
|
LogCenter.log("群打卡($groupId): $action $target")
|
||||||
) {
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
LogCenter.log("群戳一戳推送失败!", Level.WARN)
|
.transGroupSign(time, target.toLong(), action, rankImg, groupId)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群打卡推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
LogCenter.log("onGroupPokeAndGroupSign unknown type ${detail[2].asInt}", Level.WARN)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,13 +392,21 @@ internal object PrimitiveListener {
|
|||||||
val groupCode = pb[1, 3, 2, 1].asULong
|
val groupCode = pb[1, 3, 2, 1].asULong
|
||||||
val targetUid = pb[1, 3, 2, 3].asUtf8String
|
val targetUid = pb[1, 3, 2, 3].asUtf8String
|
||||||
val type = pb[1, 3, 2, 4].asInt
|
val type = pb[1, 3, 2, 4].asInt
|
||||||
val operation = ContactHelper.getUinByUidAsync(pb[1, 3, 2, 5].asUtf8String).toLong()
|
|
||||||
|
GroupSvc.getGroupMemberList(groupCode.toString(), true).onFailure {
|
||||||
|
LogCenter.log("新成员加入刷新群成员列表失败: $groupCode", Level.WARN)
|
||||||
|
}.onSuccess {
|
||||||
|
LogCenter.log("新成员加入刷新群成员列表成功,群成员数量: ${it.size}", Level.INFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
val operatorUid = pb[1, 3, 2, 5].asUtf8String
|
||||||
|
val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong()
|
||||||
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
|
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
|
||||||
LogCenter.log("群成员增加($groupCode): $target, type = $type")
|
LogCenter.log("群成员增加($groupCode): $target, type = $type")
|
||||||
|
|
||||||
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
.transGroupMemberNumChanged(
|
.transGroupMemberNumChanged(
|
||||||
time, target, groupCode, operation, NoticeType.GroupMemIncrease, when (type) {
|
time, target, targetUid, groupCode, operator, operatorUid, NoticeType.GroupMemIncrease, when (type) {
|
||||||
130 -> NoticeSubType.Approve
|
130 -> NoticeSubType.Approve
|
||||||
131 -> NoticeSubType.Invite
|
131 -> NoticeSubType.Invite
|
||||||
else -> NoticeSubType.Approve
|
else -> NoticeSubType.Approve
|
||||||
@ -347,8 +421,19 @@ internal object PrimitiveListener {
|
|||||||
val groupCode = pb[1, 3, 2, 1].asULong
|
val groupCode = pb[1, 3, 2, 1].asULong
|
||||||
val targetUid = pb[1, 3, 2, 3].asUtf8String
|
val targetUid = pb[1, 3, 2, 3].asUtf8String
|
||||||
val type = pb[1, 3, 2, 4].asInt
|
val type = pb[1, 3, 2, 4].asInt
|
||||||
val operation = ContactHelper.getUinByUidAsync(pb[1, 3, 2, 5].asUtf8String).toLong()
|
val operatorUid = try {
|
||||||
|
pb[1, 3, 2, 5, 1, 1].asUtf8String
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
pb[1, 3, 2, 5].asUtf8String
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupSvc.getGroupMemberList(groupCode.toString(), true).onFailure {
|
||||||
|
LogCenter.log("新成员加入刷新群成员列表失败: $groupCode", Level.WARN)
|
||||||
|
}.onSuccess {
|
||||||
|
LogCenter.log("新成员加入刷新群成员列表成功,群成员数量: ${it.size}", Level.INFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong()
|
||||||
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
|
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
|
||||||
val subtype = when (type) {
|
val subtype = when (type) {
|
||||||
130 -> NoticeSubType.Leave
|
130 -> NoticeSubType.Leave
|
||||||
@ -362,7 +447,7 @@ internal object PrimitiveListener {
|
|||||||
LogCenter.log("群成员减少($groupCode): $target, type = $subtype ($type)")
|
LogCenter.log("群成员减少($groupCode): $target, type = $subtype ($type)")
|
||||||
|
|
||||||
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
.transGroupMemberNumChanged(time, target, groupCode, operation, NoticeType.GroupMemDecrease, subtype)
|
.transGroupMemberNumChanged(time, target, targetUid, groupCode, operator, operatorUid, NoticeType.GroupMemDecrease, subtype)
|
||||||
) {
|
) {
|
||||||
LogCenter.log("群成员减少推送失败!", Level.WARN)
|
LogCenter.log("群成员减少推送失败!", Level.WARN)
|
||||||
}
|
}
|
||||||
@ -383,40 +468,55 @@ internal object PrimitiveListener {
|
|||||||
LogCenter.log("群管理员变动($groupCode): $target, isSetAdmin = $isSetAdmin")
|
LogCenter.log("群管理员变动($groupCode): $target, isSetAdmin = $isSetAdmin")
|
||||||
|
|
||||||
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
.transGroupAdminChanged(msgTime, target, groupCode, isSetAdmin)
|
.transGroupAdminChanged(msgTime, target, targetUid, groupCode, isSetAdmin)
|
||||||
) {
|
) {
|
||||||
LogCenter.log("群管理员变动推送失败!", Level.WARN)
|
LogCenter.log("群管理员变动推送失败!", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onGroupBan(msgTime: Long, pb: ProtoMap) {
|
private suspend fun onGroupBan(msgTime: Long, pb: ProtoMap) {
|
||||||
val groupCode = pb[1, 1, 1].asULong
|
val groupCode = pb[1, 3, 2, 1].asULong
|
||||||
val operatorUid = pb[1, 3, 2, 4].asUtf8String
|
val operatorUid = pb[1, 3, 2, 4].asUtf8String
|
||||||
val targetUid = pb[1, 3, 2, 5, 3, 1].asUtf8String
|
val wholeBan = !pb.has(1, 3, 2, 5, 3, 1)
|
||||||
val duration = pb[1, 3, 2, 5, 3, 2].asInt
|
val targetUid = if (wholeBan) "" else pb[1, 3, 2, 5, 3, 1].asUtf8String
|
||||||
val operation = ContactHelper.getUinByUidAsync(operatorUid).toLong()
|
val rawDuration = pb[1, 3, 2, 5, 3, 2].asInt
|
||||||
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
|
|
||||||
LogCenter.log("群禁言($groupCode): $operation -> $target, 时长 = ${duration}s")
|
|
||||||
|
|
||||||
|
val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong()
|
||||||
|
val duration = if (wholeBan) -1 else rawDuration
|
||||||
|
val target = if (wholeBan) 0 else ContactHelper.getUinByUidAsync(targetUid).toLong()
|
||||||
|
val subType = if (rawDuration == 0) NoticeSubType.LiftBan else NoticeSubType.Ban
|
||||||
|
|
||||||
|
if (wholeBan) {
|
||||||
|
LogCenter.log("群全员禁言($groupCode): $operator -> ${if (subType == NoticeSubType.Ban) "开启" else "关闭"}")
|
||||||
|
} else {
|
||||||
|
LogCenter.log("群禁言($groupCode): $operator -> $target, 时长 = ${duration}s")
|
||||||
|
}
|
||||||
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
.transGroupBan(msgTime, operation, target, groupCode, duration)
|
.transGroupBan(msgTime, subType, operator, operatorUid, target, targetUid, groupCode, duration)
|
||||||
) {
|
) {
|
||||||
LogCenter.log("群禁言推送失败!", Level.WARN)
|
LogCenter.log("群禁言推送失败!", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onGroupRecall(time: Long, pb: ProtoMap) {
|
private suspend fun onGroupRecall(time: Long, pb: ProtoMap) {
|
||||||
val groupCode = pb[1, 1, 1].asULong
|
var detail = pb[1, 3, 2]
|
||||||
val readPacket = ByteReadPacket(pb[1, 3, 2].asByteArray)
|
if (detail !is ProtoMap) {
|
||||||
try {
|
try {
|
||||||
/**
|
val readPacket = ByteReadPacket(detail.asByteArray)
|
||||||
* 真是不理解这个傻呗设计,有些群是正常的Protobuf,有些群要去掉7字节
|
readPacket.discardExact(4)
|
||||||
*/
|
readPacket.discardExact(1)
|
||||||
val detail = if (readPacket.readBuf32Long() == groupCode) {
|
detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
||||||
readPacket.discardExact(1)
|
readPacket.release()
|
||||||
ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
} catch (e: Exception) {
|
||||||
} else pb[1, 3, 2]
|
LogCenter.log("onGroupRecall error: ${e.stackTraceToString()}", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var groupCode:Long
|
||||||
|
try {
|
||||||
|
groupCode = detail[4].asULong
|
||||||
|
}catch (e: ClassCastException){
|
||||||
|
groupCode = detail[4].asList.value[0].asULong
|
||||||
|
}
|
||||||
val operatorUid = detail[11, 1].asUtf8String
|
val operatorUid = detail[11, 1].asUtf8String
|
||||||
val targetUid = detail[11, 3, 6].asUtf8String
|
val targetUid = detail[11, 3, 6].asUtf8String
|
||||||
val msgSeq = detail[11, 3, 1].asLong
|
val msgSeq = detail[11, 3, 1].asLong
|
||||||
@ -436,9 +536,6 @@ internal object PrimitiveListener {
|
|||||||
) {
|
) {
|
||||||
LogCenter.log("群消息撤回推送失败!", Level.WARN)
|
LogCenter.log("群消息撤回推送失败!", Level.WARN)
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
readPacket.release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onGroupApply(time: Long, pb: ProtoMap) {
|
private suspend fun onGroupApply(time: Long, pb: ProtoMap) {
|
||||||
@ -448,16 +545,18 @@ internal object PrimitiveListener {
|
|||||||
val applierUid = pb[1, 3, 2, 3].asUtf8String
|
val applierUid = pb[1, 3, 2, 3].asUtf8String
|
||||||
val reason = pb[1, 3, 2, 5].asUtf8String
|
val reason = pb[1, 3, 2, 5].asUtf8String
|
||||||
val applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
|
val applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
|
||||||
|
if (applier == getLongUin()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
LogCenter.log("入群申请($groupCode) $applier: \"$reason\"")
|
LogCenter.log("入群申请($groupCode) $applier: \"$reason\"")
|
||||||
val flag = try {
|
val flag = try {
|
||||||
var reqs = requestGroupSystemMsgNew(10, 1)
|
var reqs = requestGroupSystemMsgNew(10, 1)
|
||||||
val riskReqs = requestGroupSystemMsgNew(10, 2)
|
val riskReqs = requestGroupSystemMsgNew(5, 2)
|
||||||
reqs = reqs + riskReqs
|
reqs = reqs + riskReqs
|
||||||
val req = reqs.first {
|
val req = reqs.firstOrNull() {
|
||||||
it.msg_time.get() == time
|
it.msg_time.get() == time
|
||||||
}
|
}
|
||||||
val seq = req.msg_seq?.get()
|
val seq = req?.msg_seq?.get() ?: time
|
||||||
"$seq;$groupCode;$applier"
|
"$seq;$groupCode;$applier"
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
"$time;$groupCode;$applier"
|
"$time;$groupCode;$applier"
|
||||||
@ -473,6 +572,9 @@ internal object PrimitiveListener {
|
|||||||
val groupCode = pb[1, 3, 2, 2, 3].asULong
|
val groupCode = pb[1, 3, 2, 2, 3].asULong
|
||||||
val applierUid = pb[1, 3, 2, 2, 5].asUtf8String
|
val applierUid = pb[1, 3, 2, 2, 5].asUtf8String
|
||||||
val applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
|
val applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
|
||||||
|
if (applier == getLongUin()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (pb[1, 3, 2, 2, 1].asInt < 3) {
|
if (pb[1, 3, 2, 2, 1].asInt < 3) {
|
||||||
// todo
|
// todo
|
||||||
return
|
return
|
||||||
@ -480,17 +582,17 @@ internal object PrimitiveListener {
|
|||||||
LogCenter.log("邀请入群申请($groupCode): $applier")
|
LogCenter.log("邀请入群申请($groupCode): $applier")
|
||||||
val flag = try {
|
val flag = try {
|
||||||
var reqs = requestGroupSystemMsgNew(10, 1)
|
var reqs = requestGroupSystemMsgNew(10, 1)
|
||||||
val riskReqs = requestGroupSystemMsgNew(10, 2)
|
val riskReqs = requestGroupSystemMsgNew(5, 2)
|
||||||
reqs = reqs + riskReqs
|
reqs = reqs + riskReqs
|
||||||
val req = reqs.first {
|
val req = reqs.firstOrNull() {
|
||||||
it.msg_time.get() == time
|
it.msg_time.get() == time
|
||||||
}
|
}
|
||||||
val seq = req.msg_seq?.get()
|
val seq = req?.msg_seq?.get() ?: time
|
||||||
"$seq;$groupCode;$applier"
|
"$seq;$groupCode;$applier"
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
"$time;$groupCode;$applierUid"
|
"$time;$groupCode;$applier"
|
||||||
}
|
}
|
||||||
if (GlobalEventTransmitter.RequestTransmitter
|
if (!GlobalEventTransmitter.RequestTransmitter
|
||||||
.transGroupApply(time, applier, "", groupCode, flag, RequestSubType.Add)
|
.transGroupApply(time, applier, "", groupCode, flag, RequestSubType.Add)
|
||||||
) {
|
) {
|
||||||
LogCenter.log("邀请入群申请推送失败!", Level.WARN)
|
LogCenter.log("邀请入群申请推送失败!", Level.WARN)
|
||||||
@ -509,19 +611,19 @@ internal object PrimitiveListener {
|
|||||||
var reqs = requestGroupSystemMsgNew(10, 1)
|
var reqs = requestGroupSystemMsgNew(10, 1)
|
||||||
val riskReqs = requestGroupSystemMsgNew(10, 2)
|
val riskReqs = requestGroupSystemMsgNew(10, 2)
|
||||||
reqs = reqs + riskReqs
|
reqs = reqs + riskReqs
|
||||||
val req = reqs.first {
|
val req = reqs.firstOrNull() {
|
||||||
it.msg_time.get() == time
|
it.msg_time.get() == time
|
||||||
}
|
}
|
||||||
val seq = req.msg_seq?.get()
|
val seq = req?.msg_seq?.get() ?: time
|
||||||
"$seq;$groupCode;$uin"
|
"$seq;$groupCode;$uin"
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
"$time;$groupCode;$uin"
|
"$time;$groupCode;$uin"
|
||||||
}
|
}
|
||||||
if (GlobalEventTransmitter.RequestTransmitter
|
if (!GlobalEventTransmitter.RequestTransmitter
|
||||||
.transGroupApply(time, invitor, "", groupCode, flag, RequestSubType.Invite)
|
.transGroupApply(time, invitor, "", groupCode, flag, RequestSubType.Invite)
|
||||||
) {
|
) {
|
||||||
LogCenter.log("邀请入群推送失败!", Level.WARN)
|
LogCenter.log("邀请入群推送失败!", Level.WARN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ annotation class ShamrockDsl
|
|||||||
|
|
||||||
private val keyIsJson = AttributeKey<Boolean>("isJson")
|
private val keyIsJson = AttributeKey<Boolean>("isJson")
|
||||||
private val keyJsonObject = AttributeKey<JsonObject>("paramsJson")
|
private val keyJsonObject = AttributeKey<JsonObject>("paramsJson")
|
||||||
|
private val keyJsonArray = AttributeKey<JsonArray>("paramsJsonArray")
|
||||||
|
private val keyJsonElement = AttributeKey<JsonElement>("paramsJsonElement")
|
||||||
private val keyParts = AttributeKey<Parameters>("paramsParts")
|
private val keyParts = AttributeKey<Parameters>("paramsParts")
|
||||||
|
|
||||||
suspend fun ApplicationCall.fetch(key: String): String {
|
suspend fun ApplicationCall.fetch(key: String): String {
|
||||||
@ -167,7 +169,9 @@ suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostOrThrow(key: String)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun PipelineContext<Unit, ApplicationCall>.isJsonData(): Boolean {
|
fun PipelineContext<Unit, ApplicationCall>.isJsonData(): Boolean {
|
||||||
return ContentType.Application.Json == call.request.contentType() || (keyIsJson in call.attributes && call.attributes[keyIsJson])
|
return ContentType.Application.Json == call.request.contentType()
|
||||||
|
|| (keyIsJson in call.attributes && call.attributes[keyIsJson])
|
||||||
|
|| (keyJsonElement in call.attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun PipelineContext<Unit, ApplicationCall>.isJsonString(key: String): Boolean {
|
suspend fun PipelineContext<Unit, ApplicationCall>.isJsonString(key: String): Boolean {
|
||||||
@ -245,12 +249,33 @@ suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonObjectOrNull(key
|
|||||||
call.attributes[keyJsonObject]
|
call.attributes[keyJsonObject]
|
||||||
} else {
|
} else {
|
||||||
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
|
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
|
||||||
|
call.attributes.put(keyIsJson, true)
|
||||||
call.attributes.put(keyJsonObject, it)
|
call.attributes.put(keyJsonObject, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data[key].asJsonObjectOrNull
|
return data[key].asJsonObjectOrNull
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonElementOrNull(): JsonElement? {
|
||||||
|
return runCatching {
|
||||||
|
if (call.attributes.contains(keyJsonObject)) {
|
||||||
|
call.attributes[keyJsonObject]
|
||||||
|
} else if (call.attributes.contains(keyJsonArray)) {
|
||||||
|
call.attributes[keyJsonArray]
|
||||||
|
} else if (call.attributes.contains(keyJsonElement)) {
|
||||||
|
call.attributes[keyJsonElement]
|
||||||
|
} else {
|
||||||
|
Json.parseToJsonElement(call.receiveText()).also {
|
||||||
|
call.attributes.put(keyJsonElement, it)
|
||||||
|
if (it is JsonObject) {
|
||||||
|
call.attributes.put(keyJsonObject, it)
|
||||||
|
} else if (it is JsonArray) {
|
||||||
|
call.attributes.put(keyJsonArray, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonArray(key: String): JsonArray {
|
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonArray(key: String): JsonArray {
|
||||||
val data = if (call.attributes.contains(keyJsonObject)) {
|
val data = if (call.attributes.contains(keyJsonObject)) {
|
||||||
|
@ -196,3 +196,46 @@ internal fun Any.toInnerValuesString(): String {
|
|||||||
builder.append("=======================>\n")
|
builder.append("=======================>\n")
|
||||||
return builder.toString()
|
return builder.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun Any.toInnerValuesString(clz: Class<*>): String {
|
||||||
|
val builder = StringBuilder()
|
||||||
|
builder.append(clz.canonicalName)
|
||||||
|
builder.append("========>\n")
|
||||||
|
clz.declaredFields.forEach {
|
||||||
|
if (!Modifier.isStatic(it.modifiers)) {
|
||||||
|
if (!it.isAccessible) {
|
||||||
|
it.isAccessible = true
|
||||||
|
}
|
||||||
|
builder.append(it.name)
|
||||||
|
builder.append(" = ")
|
||||||
|
when (val v = it.get(this)) {
|
||||||
|
null -> builder.append("null")
|
||||||
|
is ByteArray -> builder.append(v.toHexString())
|
||||||
|
is Map<*, *> -> {
|
||||||
|
builder.append("{\n\t")
|
||||||
|
v.forEach { key, value ->
|
||||||
|
builder.append("\t")
|
||||||
|
builder.append(key)
|
||||||
|
builder.append(" = ")
|
||||||
|
builder.append(value)
|
||||||
|
builder.append("\n")
|
||||||
|
}
|
||||||
|
builder.append("}")
|
||||||
|
}
|
||||||
|
is List<*> -> {
|
||||||
|
builder.append("[\n\t")
|
||||||
|
v.forEach { value ->
|
||||||
|
builder.append("\t")
|
||||||
|
builder.append(value)
|
||||||
|
builder.append("\n")
|
||||||
|
}
|
||||||
|
builder.append("]")
|
||||||
|
}
|
||||||
|
else -> builder.append(v)
|
||||||
|
}
|
||||||
|
builder.append("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.append("=======================>\n")
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.utils
|
||||||
|
|
||||||
|
object CryptTools {
|
||||||
|
|
||||||
|
fun getSHA1(string: String): ByteArray {
|
||||||
|
return getSHA1(string.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSHA1(bytes: ByteArray): ByteArray {
|
||||||
|
return getDigest(bytes, "SHA-1")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDigest(bytes: ByteArray, algorithm: String): ByteArray {
|
||||||
|
val digest = java.security.MessageDigest.getInstance(algorithm)
|
||||||
|
digest.update(bytes)
|
||||||
|
return digest.digest()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,9 +4,11 @@ import java.io.ByteArrayInputStream
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.zip.Deflater
|
import java.util.zip.Deflater
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.GZIPOutputStream
|
||||||
import java.util.zip.Inflater
|
import java.util.zip.Inflater
|
||||||
|
|
||||||
|
|
||||||
object DeflateTools {
|
object DeflateTools {
|
||||||
fun uncompress(inputByte: ByteArray?): ByteArray {
|
fun uncompress(inputByte: ByteArray?): ByteArray {
|
||||||
var len: Int
|
var len: Int
|
||||||
@ -79,4 +81,20 @@ object DeflateTools {
|
|||||||
input.close()
|
input.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ungzip(bytes: ByteArray): ByteArray {
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
val `in` = ByteArrayInputStream(bytes)
|
||||||
|
try {
|
||||||
|
val ungzip = GZIPInputStream(`in`)
|
||||||
|
val buffer = ByteArray(256)
|
||||||
|
var n: Int
|
||||||
|
while (ungzip.read(buffer).also { n = it } >= 0) {
|
||||||
|
out.write(buffer, 0, n)
|
||||||
|
}
|
||||||
|
} catch (e: java.lang.Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return out.toByteArray()
|
||||||
|
}
|
||||||
}
|
}
|
@ -4,4 +4,6 @@ object MD5 {
|
|||||||
external fun getMd5Hex(bytes: ByteArray): String
|
external fun getMd5Hex(bytes: ByteArray): String
|
||||||
|
|
||||||
external fun genFileMd5Hex(filePath: String): String
|
external fun genFileMd5Hex(filePath: String): String
|
||||||
|
|
||||||
|
external fun genFileMd5(filePath: String): ByteArray
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user