mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
91 Commits
1.0.7
...
48b720bdd7
Author | SHA1 | Date | |
---|---|---|---|
48b720bdd7 | |||
2038d81ce8 | |||
81be383b5f | |||
0fb88e3e44 | |||
e92b04ad0f | |||
160d1a11ac | |||
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:
|
||||
build:
|
||||
name: Build APK
|
||||
name: Build Shamrock
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: "temurin"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v4.0.0
|
||||
with:
|
||||
java-version: 17
|
||||
cache: 'gradle'
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Setup cmake
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "cmake;3.22.1" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
echo "sdk.dir=${ANDROID_HOME}" > local.properties
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
|
||||
restore-keys: gradle-deps
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2.9.0
|
||||
- name: Cache Gradle Build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
~/.gradle/buildOutputCleanup/cache.properties
|
||||
key: gradle-builds-core-${{ github.sha }}
|
||||
restore-keys: gradle-builds
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
@ -46,42 +60,41 @@ jobs:
|
||||
KEY_ALIAS: ${{ secrets.SIGN_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
|
||||
|
||||
- name: Install aapt
|
||||
run: sudo apt-get update && sudo apt-get install -y aapt
|
||||
|
||||
- name: Set Shamrock Version
|
||||
run: |
|
||||
apk_file=${{ env.APK_FILE_ALL }}
|
||||
apk_dump=$(aapt dump badging "$apk_file")
|
||||
version_name=$(sed -n "s/.*versionName='\([^']*\)'.*/\1/p" <<< "$apk_dump")
|
||||
echo "SHAMROCK_VERSION=$version_name" >> $GITHUB_ENV
|
||||
version_name_all=$(basename -s .apk "${{ env.APK_FILE_ALL }}")
|
||||
version_name_arm64=$(basename -s .apk "${{ env.APK_FILE_ARM64 }}")
|
||||
version_name_x86_64=$(basename -s .apk "${{ env.APK_FILE_X86_64 }}")
|
||||
echo "SHAMROCK_VERSION_ALL=$version_name_all" >> $GITHUB_ENV
|
||||
echo "SHAMROCK_VERSION_ARM64=$version_name_arm64" >> $GITHUB_ENV
|
||||
echo "SHAMROCK_VERSION_x86_64=$version_name_x86_64" >> $GITHUB_ENV
|
||||
|
||||
- name: Show Artifacts SHA256
|
||||
run: |
|
||||
echo "### Build Success :rocket:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|ABI|SHA256|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|:--------:|:----------|" >> $GITHUB_STEP_SUMMARY
|
||||
all=($(sha256sum ${{ env.APK_FILE_ALL }}))
|
||||
all=($(sha256sum "${{ env.APK_FILE_ALL }}"))
|
||||
echo "|all|$all" >> $GITHUB_STEP_SUMMARY
|
||||
arm64=($(sha256sum ${{ env.APK_FILE_ARM64 }}))
|
||||
arm64=($(sha256sum "${{ env.APK_FILE_ARM64 }}"))
|
||||
echo "|arm64|$arm64" >> $GITHUB_STEP_SUMMARY
|
||||
x86_64=($(sha256sum ${{ env.APK_FILE_X86_64 }}))
|
||||
x86_64=($(sha256sum "${{ env.APK_FILE_X86_64 }}"))
|
||||
echo "|x86_64|$x86_64" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload ALL APK RELEASE
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-all
|
||||
path: ${{ env.APK_FILE_ALL }}
|
||||
name: "${{ env.SHAMROCK_VERSION_ALL }}"
|
||||
path: "${{ env.APK_FILE_ALL }}"
|
||||
|
||||
- name: Upload ARM64 APK RELEASE
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-arm64
|
||||
path: ${{ env.APK_FILE_ARM64 }}
|
||||
name: "${{ env.SHAMROCK_VERSION_ARM64 }}"
|
||||
path: "${{ env.APK_FILE_ARM64 }}"
|
||||
|
||||
- name: Upload X86_64 APK RELEASE
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Shamrock-v${{ env.SHAMROCK_VERSION }}-x86_64
|
||||
path: ${{ env.APK_FILE_X86_64 }}
|
||||
name: "${{ env.SHAMROCK_VERSION_x86_64 }}"
|
||||
path: "${{ env.APK_FILE_X86_64 }}"
|
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小时内删除。
|
||||
> 本项目目的是研究 Xposed 和 LSPosed 框架的使用。 Epic 框架开发相关知识。
|
||||
> Riru可能导致封禁,请减少使用。
|
||||
> 如有违反法律,请联系删除。
|
||||
> 请勿在任何平台宣传,宣扬,转发本项目,请勿恶意修改企业安装包造成相关企业产生损失,如有违背,必将追责到底。
|
||||
> 官方论坛,[点我直达](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/
|
||||
|
||||
[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
|
||||
|
||||
|
@ -1,12 +1,5 @@
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
|
||||
fun gitCommitHash(): String {
|
||||
val builder = ProcessBuilder("git", "rev-parse", "--short", "HEAD")
|
||||
val process = builder.start()
|
||||
val reader = process.inputReader()
|
||||
val hash = reader.readText().trim()
|
||||
return if (hash.isNotEmpty()) ".$hash" else ""
|
||||
}
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
@ -17,19 +10,20 @@ plugins {
|
||||
android {
|
||||
namespace = "moe.fuqiuluo.shamrock"
|
||||
ndkVersion = "25.1.8937393"
|
||||
compileSdk = 33
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "moe.fuqiuluo.shamrock"
|
||||
minSdk = 24
|
||||
targetSdk = 33
|
||||
versionCode = (System.currentTimeMillis() / 1000).toInt()
|
||||
versionName = "1.0.7-dev" + gitCommitHash()
|
||||
minSdk = 27
|
||||
targetSdk = 34
|
||||
versionCode = getVersionCode()
|
||||
versionName = "1.0.7" + ".r${getGitCommitCount()}." + getVersionName()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
@Suppress("UnstableApiUsage")
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cppFlags += ""
|
||||
@ -50,8 +44,7 @@ android {
|
||||
android.applicationVariants.all {
|
||||
outputs.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
|
||||
.forEach {
|
||||
val abi = it.outputFileName.split("-")[1].split(".apk")[0]
|
||||
val abiName = when (abi) {
|
||||
val abiName = when (val abi = it.outputFileName.split("-")[1].split(".apk")[0]) {
|
||||
"app" -> "all"
|
||||
"x64" -> "x86_64"
|
||||
else -> abi
|
||||
@ -69,6 +62,7 @@ android {
|
||||
println("Full architecture and full compilation.")
|
||||
abiFilters.add("arm64-v8a")
|
||||
abiFilters.add("x86_64")
|
||||
abiFilters.add("x86")
|
||||
}
|
||||
}
|
||||
create("arm64") {
|
||||
@ -133,6 +127,11 @@ android {
|
||||
}
|
||||
|
||||
configureAppSigningConfigsForRelease(project)
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun configureAppSigningConfigsForRelease(project: Project) {
|
||||
@ -154,10 +153,39 @@ fun configureAppSigningConfigsForRelease(project: Project) {
|
||||
release {
|
||||
signingConfig = signingConfigs.findByName("release")
|
||||
}
|
||||
debug {
|
||||
signingConfig = signingConfigs.findByName("release")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getGitCommitCount(): Int {
|
||||
val out = ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine("git", "rev-list", "--count", "HEAD")
|
||||
standardOutput = out
|
||||
}
|
||||
return out.toString().trim().toInt()
|
||||
}
|
||||
|
||||
fun getGitCommitHash(): String {
|
||||
val out = ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine("git", "rev-parse", "--short", "HEAD")
|
||||
standardOutput = out
|
||||
}
|
||||
return out.toString().trim()
|
||||
}
|
||||
|
||||
fun getVersionCode(): Int {
|
||||
return (System.currentTimeMillis() / 1000L).toInt()
|
||||
}
|
||||
|
||||
fun getVersionName(): String {
|
||||
return getGitCommitHash()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
||||
|
@ -3,10 +3,6 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:name=".app.MyApplication"
|
||||
@ -19,7 +15,6 @@
|
||||
android:theme="@style/Theme.Shamrock"
|
||||
android:zygotePreloadName="@string/app_name"
|
||||
android:multiArch="true"
|
||||
android:extractNativeLibs="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
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) {
|
||||
std::string cache;
|
||||
bool is_start = false;
|
||||
std::string key_tmp;
|
||||
std::unordered_map<std::string, std::string> kv;
|
||||
for(int i = 0; i < code.size(); i++) {
|
||||
auto c = code[i];
|
||||
if (c == '[') {
|
||||
for(size_t i = 0; i < code.size(); i++) {
|
||||
int utf8_char_len = utf8_next_len(code, i);
|
||||
if(utf8_char_len == 0) {
|
||||
continue;
|
||||
}
|
||||
std::string_view c(&code[i],utf8_char_len);
|
||||
if (c == "[") {
|
||||
if (is_start) {
|
||||
throw illegal_code();
|
||||
} else {
|
||||
if (!cache.empty()) {
|
||||
std::unordered_map<std::string, std::string> kv;
|
||||
replace_string(cache, "[", "[");
|
||||
replace_string(cache, "]", "]");
|
||||
replace_string(cache, "&", "&");
|
||||
kv.emplace("_type", "text");
|
||||
kv.emplace("text", cache);
|
||||
dest.push_back(kv);
|
||||
cache.clear();
|
||||
}
|
||||
auto c1 = code[i + 1];
|
||||
if (c1 == 'C') {
|
||||
i++;
|
||||
auto c2 = code[i + 1];
|
||||
if(c2 == 'Q') {
|
||||
i++;
|
||||
auto c3 = code[i + 1];
|
||||
if (c3 == ':') {
|
||||
i++;
|
||||
is_start = true;
|
||||
} else {
|
||||
cache += c;
|
||||
cache += c1;
|
||||
cache += c2;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
cache += c;
|
||||
cache += c1;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
std::string_view cq_flag(&code[i],4);
|
||||
if(cq_flag == "[CQ:"){
|
||||
is_start = true;
|
||||
i += 3;
|
||||
}else{
|
||||
cache += c;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c == '=') {
|
||||
else if (c == "=") {
|
||||
if (is_start) {
|
||||
if (cache.empty()) {
|
||||
throw illegal_code();
|
||||
@ -70,17 +79,17 @@ void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::
|
||||
cache += c;
|
||||
}
|
||||
}
|
||||
else if (c == ',') {
|
||||
else if (c == ",") {
|
||||
if (is_start) {
|
||||
if (kv.count("_type") == 0 && !cache.empty()) {
|
||||
kv.emplace("_type", cache);
|
||||
cache.clear();
|
||||
} else {
|
||||
if (!key_tmp.empty()) {
|
||||
replace_string(cache, "&", "&");
|
||||
replace_string(cache, "[", "[");
|
||||
replace_string(cache, "]", "]");
|
||||
replace_string(cache, ",", ",");
|
||||
replace_string(cache, "&", "&");
|
||||
kv.emplace(key_tmp, cache);
|
||||
cache.clear();
|
||||
key_tmp.clear();
|
||||
@ -90,14 +99,14 @@ void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::
|
||||
cache += c;
|
||||
}
|
||||
}
|
||||
else if (c == ']') {
|
||||
else if (c == "]") {
|
||||
if (is_start) {
|
||||
if (!cache.empty()) {
|
||||
if (!key_tmp.empty()) {
|
||||
replace_string(cache, "&", "&");
|
||||
replace_string(cache, "[", "[");
|
||||
replace_string(cache, "]", "]");
|
||||
replace_string(cache, ",", ",");
|
||||
replace_string(cache, "&", "&");
|
||||
kv.emplace(key_tmp, cache);
|
||||
} else {
|
||||
kv.emplace("_type", cache);
|
||||
@ -114,10 +123,14 @@ void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::
|
||||
}
|
||||
else {
|
||||
cache += c;
|
||||
i += (utf8_char_len - 1);
|
||||
}
|
||||
}
|
||||
if (!cache.empty()) {
|
||||
std::unordered_map<std::string, std::string> kv;
|
||||
replace_string(cache, "[", "[");
|
||||
replace_string(cache, "]", "]");
|
||||
replace_string(cache, "&", "&");
|
||||
kv.emplace("_type", "text");
|
||||
kv.emplace("text", cache);
|
||||
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());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_moe_fuqiuluo_shamrock_utils_MD5_genFileMd5(JNIEnv *env, jobject thiz, jstring file_path) {
|
||||
auto cPathStr = env->GetStringUTFChars(file_path, nullptr);
|
||||
std::filesystem::path filePath(cPathStr);
|
||||
if (!std::filesystem::exists(filePath)) {
|
||||
jclass exClass = env->FindClass("java/io/FileNotFoundException");
|
||||
env->ThrowNew(exClass, "目标文件不存在");
|
||||
env->DeleteLocalRef(exClass);
|
||||
return nullptr;
|
||||
}
|
||||
auto file = std::ifstream(filePath.c_str(), std::ios::binary);
|
||||
MD5 md5;
|
||||
md5.update(file);
|
||||
auto md5Bytes = md5.digest();
|
||||
auto jByteArray = env->NewByteArray(16);
|
||||
env->SetByteArrayRegion(jByteArray, 0, 16, reinterpret_cast<const jbyte*>(md5Bytes));
|
||||
return jByteArray;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_moe_fuqiuluo_shamrock_utils_MD5_getMd5Hex(JNIEnv *env, jobject thiz, jbyteArray bytes) {
|
||||
|
@ -52,6 +52,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
@ -120,7 +121,7 @@ private fun AppMainView() {
|
||||
val coreVersion = remember { mutableStateOf(getShamrockVersion(context)) }
|
||||
val coreName = remember { mutableStateOf("Xposed") }
|
||||
val voiceSwitch = remember { mutableStateOf(false) }
|
||||
@Suppress("LocalVariableName") val LocalString = LocalString
|
||||
@Suppress("LocalVariableName") val LocalString = LocalString.init()
|
||||
|
||||
if (!AppRuntime.isInit) {
|
||||
AppRuntime.state = remember {
|
||||
@ -147,7 +148,7 @@ private fun AppMainView() {
|
||||
|
||||
AppRuntime.requestCount = remember { mutableIntStateOf(0) }
|
||||
|
||||
AppRuntime.isInit = false
|
||||
AppRuntime.isInit = true
|
||||
}
|
||||
|
||||
val ctx = LocalContext.current
|
||||
|
@ -279,19 +279,19 @@ object ShamrockConfig {
|
||||
return preferences.getBoolean("enable_self_msg", false)
|
||||
}
|
||||
|
||||
fun enableSyncMsgAsSentMsg(ctx: Context): Boolean {
|
||||
val preferences = ctx.getSharedPreferences("config", 0)
|
||||
return preferences.getBoolean("enable_sync_msg_as_sent_msg", false)
|
||||
}
|
||||
|
||||
fun setEnableSelfMsg(ctx: Context, v: Boolean) {
|
||||
val preferences = ctx.getSharedPreferences("config", 0)
|
||||
preferences.edit().putBoolean("enable_self_msg", v).apply()
|
||||
}
|
||||
|
||||
fun isEchoNumber(ctx: Context): Boolean {
|
||||
fun setEnableSyncMsgAsSentMsg(ctx: Context, v: Boolean) {
|
||||
val preferences = ctx.getSharedPreferences("config", 0)
|
||||
return preferences.getBoolean("echo_number", false)
|
||||
}
|
||||
|
||||
fun setEchoNumber(ctx: Context, v: Boolean) {
|
||||
val preferences = ctx.getSharedPreferences("config", 0)
|
||||
preferences.edit().putBoolean("echo_number", v).apply()
|
||||
preferences.edit().putBoolean("enable_sync_msg_as_sent_msg", v).apply()
|
||||
}
|
||||
|
||||
fun getConfigMap(ctx: Context): Map<String, Any?> {
|
||||
@ -321,6 +321,7 @@ object ShamrockConfig {
|
||||
"echo_number" to preferences.getBoolean("echo_number", false),
|
||||
"shell" to preferences.getBoolean("shell", false),
|
||||
"alive_reply" to preferences.getBoolean("alive_reply", false),
|
||||
"enable_sync_msg_as_sent_msg" to preferences.getBoolean("enable_sync_msg_as_sent_msg", false),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -278,9 +278,7 @@ private fun APIInfoCard(
|
||||
text = authToken,
|
||||
hint = "请填写鉴权token",
|
||||
error = "输入的参数不合法",
|
||||
checker = {
|
||||
it.length in 0 .. 36
|
||||
},
|
||||
checker = { true },
|
||||
confirm = {
|
||||
ShamrockConfig.setToken(ctx, authToken.value)
|
||||
AppRuntime.log("设置鉴权Token为[${authToken.value}]。")
|
||||
|
@ -275,6 +275,17 @@ fun LabFragment() {
|
||||
return@Function true
|
||||
}
|
||||
|
||||
Function(
|
||||
title = "同步消息推送类型异换",
|
||||
desc = "推送来自同号异设备消息,将同步消息作为自发消息推送。",
|
||||
descColor = it,
|
||||
isSwitch = ShamrockConfig.enableSyncMsgAsSentMsg(ctx)
|
||||
) {
|
||||
ShamrockConfig.setEnableSyncMsgAsSentMsg(ctx, it)
|
||||
ShamrockConfig.pushUpdate(ctx)
|
||||
return@Function true
|
||||
}
|
||||
|
||||
/*
|
||||
Function(
|
||||
title = "使用纯数字ECHO",
|
||||
|
@ -8,6 +8,7 @@ import android.os.Bundle
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@ -58,7 +59,8 @@ object DashboardInitializer {
|
||||
url("http://127.0.0.1:$servicePort/get_account_info")
|
||||
val token = ShamrockConfig.getToken(context)
|
||||
if (token.isNotBlank()) {
|
||||
header("Authorization", "Bearer $token")
|
||||
//header("Authorization", "Bearer $token")
|
||||
parameter("access_token", token)
|
||||
}
|
||||
}.let {
|
||||
if (it.status == HttpStatusCode.OK) {
|
||||
|
@ -6,7 +6,9 @@ package moe.fuqiuluo.shamrock.ui.theme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import moe.fuqiuluo.shamrock.R
|
||||
|
||||
private val LocalStringDefault = Default()
|
||||
@ -164,4 +166,14 @@ open class VarString(
|
||||
|
||||
var persistentText: 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.
|
||||
plugins {
|
||||
id("com.android.application") version "8.1.2" apply false
|
||||
id("com.android.application") version "8.2.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
|
||||
id("com.android.library") version "8.1.2" apply false
|
||||
id("com.android.library") version "8.2.0" apply false
|
||||
//id("io.realm.kotlin") version "1.11.0" apply false
|
||||
}
|
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
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
|
3
gradlew
vendored
3
gradlew
vendored
@ -83,7 +83,8 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
@ -5,10 +5,10 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "moe.fuqiuluo.qqinterface"
|
||||
compileSdk = 33
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
minSdk = 27
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static PBFloatField initFloat(float paramFloat) {
|
||||
return new PBFloatField(paramFloat, false);
|
||||
}
|
||||
|
||||
public static PBBoolField initBool(boolean z) {
|
||||
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) {
|
||||
this.content = "";
|
||||
this.content = str;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package mqq.manager;
|
||||
import android.content.Context;
|
||||
|
||||
import oicq.wlogin_sdk.request.Ticket;
|
||||
import oicq.wlogin_sdk.request.WtTicketPromise;
|
||||
|
||||
public interface TicketManager extends Manager {
|
||||
String getA2(String uin);
|
||||
@ -15,6 +16,8 @@ public interface TicketManager extends Manager {
|
||||
|
||||
String getPskey(String uin, String domain);
|
||||
|
||||
Ticket getPskey(String str, long j2, String[] strArr, WtTicketPromise wtTicketPromise);
|
||||
|
||||
String getPt4Token(String uin, String domain);
|
||||
|
||||
String getSkey(String uin); // 假的Skey
|
||||
|
@ -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 {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -8,12 +8,13 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "moe.fuqiuluo.xposed"
|
||||
compileSdk = 33
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
minSdk = 27
|
||||
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
@Suppress("UnstableApiUsage")
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cppFlags += ""
|
||||
|
@ -15,6 +15,8 @@ static std::vector<std::string> qemu_detect_props = {
|
||||
|
||||
static int (*backup_system_property_get)(const char *name, char *value);
|
||||
static FILE* (*backup_fopen)(const char *filename, const char *mode);
|
||||
static int (*backup_memcmp)(const void* __lhs, const void* __rhs, size_t __n);
|
||||
//static const char* (*backup_strstr)(const char* h, const char* n);
|
||||
|
||||
//int fake_system_property_get(const char *name, char *value);
|
||||
//FILE* fake_fopen(const char *filename, const char *mode);
|
||||
|
@ -71,6 +71,7 @@ int fake_system_property_get(const char *name, char *value) {
|
||||
|| strstr(value, "unknown")
|
||||
|| strstr(value, "emulator")
|
||||
|| strstr(value, "vbox")
|
||||
|| strstr(value, "nox") //部分NoxAppPlayer
|
||||
|| strstr(value, "genymotion")
|
||||
|| strstr(value, "goldfish")) {
|
||||
strcpy(value, "qcom");
|
||||
@ -84,22 +85,84 @@ int fake_system_property_get(const char *name, char *value) {
|
||||
return backup_system_property_get(name, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
FILE* fake_fopen(const char *filename, const char *mode) {
|
||||
if (strstr(filename, "qemu_pipe")) {
|
||||
LOGI("[Shamrock] bypass qemu detection");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (strstr(filename, "libhoudini.so")) {
|
||||
LOGI("[Shamrock] bypass emu detection");
|
||||
return nullptr;
|
||||
const char* emuSpecFile[] = {
|
||||
"libhoudini.so",
|
||||
"libndk.so",
|
||||
"libnoxd.so", //NoxAppPlayer
|
||||
"libnoxspeedup.so", //NoxAppPlayer
|
||||
"nox-prop", //NoxAppPlayer (MayUseless?)
|
||||
"nox-vbox-sf", //NoxAppPlayer (MayUseless?)
|
||||
"noxd", //NoxAppPlayer (MayUseless?)
|
||||
"noxspeedup", //NoxAppPlayer (MayUseless?)
|
||||
};
|
||||
for (const char* keyword : emuSpecFile) {
|
||||
if (strstr(filename, keyword)) {
|
||||
LOGI("[Shamrock] bypass emu detection");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (strstr(filename, "libdobby.so")) {
|
||||
LOGI("[Shamrock] bypass dobby detection");
|
||||
return nullptr;
|
||||
}
|
||||
return backup_fopen(filename, mode);
|
||||
}
|
||||
|
||||
char * __cdecl my_strstr(const char *lhs, const char *rhs) {
|
||||
char *cur = (char *)lhs;
|
||||
char *l;
|
||||
char *r;
|
||||
if (!*rhs) {
|
||||
return ((char *)lhs);
|
||||
}
|
||||
while (*cur) {
|
||||
l = cur;
|
||||
r = (char *)rhs;
|
||||
while (*r && !(*l - *r)) {
|
||||
l++;
|
||||
r++;
|
||||
}
|
||||
if (!*r) {
|
||||
return cur;
|
||||
}
|
||||
cur++;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int fake_memcmp(const void* __lhs, const void* __rhs, size_t __n) {
|
||||
//if (my_strstr((const char*) __rhs, "lsposed")) {
|
||||
//return -1;
|
||||
//}
|
||||
//if (my_strstr((const char*) __rhs, "xposed")) {
|
||||
// return -1;
|
||||
//}
|
||||
if (my_strstr((const char*) __rhs, "shamrock")) {
|
||||
if (backup_memcmp(__lhs, __rhs, __n) == 0) {
|
||||
// 底层广播判断
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (my_strstr((const char*) __rhs, "riru")) {
|
||||
return -1;
|
||||
}
|
||||
//if (my_strstr((const char*) __rhs, "zygisk")) {
|
||||
// return -1;
|
||||
//}
|
||||
//if (my_strstr((const char*) __rhs, "magisk")) {
|
||||
// return -1;
|
||||
//}
|
||||
return backup_memcmp(__lhs, __rhs, __n);
|
||||
}
|
||||
|
||||
void on_library_loaded(const char *name, void *handle) {
|
||||
|
||||
}
|
||||
@ -119,5 +182,8 @@ Java_moe_fuqiuluo_shamrock_xposed_actions_AntiDetection_antiNativeDetections(JNI
|
||||
if (hook_function == nullptr) return false;
|
||||
hook_function((void*) __system_property_get, (void *)fake_system_property_get, (void **) &backup_system_property_get);
|
||||
hook_function((void*) fopen, (void*) fake_fopen, (void**) &backup_fopen);
|
||||
//hook_function((void*) strstr, (void*) fake_strstr, (void**) &backup_strstr);
|
||||
hook_function((void*) memcmp, (void*) fake_memcmp, (void**) &backup_memcmp);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ class ProtoMap(
|
||||
var curMap = value
|
||||
tags.forEachIndexed { index, tag ->
|
||||
if (index == tags.size - 1) {
|
||||
return curMap[tag] ?: error("Tag $tag not found")
|
||||
return curMap[tag] ?: error("pb[${tags.joinToString(", ")}][$index] Tag $tag not found")
|
||||
}
|
||||
curMap[tag]?.let { v ->
|
||||
if (v is ProtoMap) {
|
||||
@ -69,7 +69,7 @@ class ProtoMap(
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
} ?: error("Tag $tag not found")
|
||||
} ?: error("pb[${tags.joinToString(", ")}][$index] Tag $tag not found")
|
||||
}
|
||||
error("Instance is not ProtoMap for get(${tags.first()})")
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ internal object CardSvc: BaseSvc() {
|
||||
val dataService = app
|
||||
.getRuntimeService(IProfileDataService::class.java, "all")
|
||||
val card = refreshCardLock.withLock {
|
||||
suspendCancellableCoroutine<Card?> {
|
||||
suspendCancellableCoroutine {
|
||||
app.addObserver(object: ProfileCardObserver() {
|
||||
override fun onGetProfileCard(success: Boolean, obj: Any) {
|
||||
app.removeObserver(this)
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
package moe.fuqiuluo.qqinterface.servlet
|
||||
|
||||
import androidx.core.text.HtmlCompat
|
||||
import com.tencent.common.app.AppInterface
|
||||
import com.tencent.mobileqq.app.BusinessHandlerFactory
|
||||
import com.tencent.mobileqq.app.QQAppInterface
|
||||
@ -46,7 +47,11 @@ import moe.fuqiuluo.proto.ProtoUtils
|
||||
import moe.fuqiuluo.proto.asInt
|
||||
import moe.fuqiuluo.proto.asUtf8String
|
||||
import moe.fuqiuluo.proto.protobufOf
|
||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getLongUin
|
||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getUin
|
||||
import moe.fuqiuluo.qqinterface.servlet.entries.GroupAtAllRemainInfo
|
||||
import moe.fuqiuluo.qqinterface.servlet.entries.ProhibitedMemberInfo
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.EssenceMessage
|
||||
@ -65,13 +70,19 @@ import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||
import moe.fuqiuluo.shamrock.tools.putBuf32Long
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||
import mqq.app.MobileQQ
|
||||
import tencent.im.group.group_member_info
|
||||
import tencent.im.oidb.cmd0x899.oidb_0x899
|
||||
import tencent.im.oidb.cmd0x89a.oidb_0x89a
|
||||
import tencent.im.oidb.cmd0x8a0.oidb_0x8a0
|
||||
import tencent.im.oidb.cmd0x8a7.cmd0x8a7
|
||||
import tencent.im.oidb.cmd0x8fc.Oidb_0x8fc
|
||||
import tencent.im.oidb.cmd0xeb7.oidb_0xeb7
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import tencent.im.troop.honor.troop_honor
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Modifier
|
||||
import java.nio.ByteBuffer
|
||||
@ -94,6 +105,27 @@ internal object GroupSvc: BaseSvc() {
|
||||
private lateinit var METHOD_REQ_TROOP_MEM_LIST: Method
|
||||
private lateinit var METHOD_REQ_MODIFY_GROUP_NAME: Method
|
||||
|
||||
suspend fun getGroupRemainAtAllRemain (groupId: Long): Result<GroupAtAllRemainInfo> {
|
||||
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x8a7_0", 2215, 0, cmd0x8a7.ReqBody().apply {
|
||||
uint32_sub_cmd.set(1)
|
||||
uint32_limit_interval_type_for_uin.set(2)
|
||||
uint32_limit_interval_type_for_group.set(1)
|
||||
uint64_uin.set(getLongUin())
|
||||
uint64_group_code.set(groupId)
|
||||
}.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(buffer.slice(4))
|
||||
if(body.uint32_result.get() != 0) {
|
||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
||||
}
|
||||
|
||||
val resp = cmd0x8a7.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return Result.success(GroupAtAllRemainInfo(
|
||||
canAtAll = resp.bool_can_at_all.get(),
|
||||
remainAtAllCountForGroup = resp.uint32_remain_at_all_count_for_group.get(),
|
||||
remainAtAllCountForUin = resp.uint32_remain_at_all_count_for_uin.get()
|
||||
))
|
||||
}
|
||||
suspend fun getProhibitedMemberList(groupId: Long): Result<List<ProhibitedMemberInfo>> {
|
||||
val buffer = sendOidbAW("OidbSvc.0x899_0", 2201, 0, oidb_0x899.ReqBody().apply {
|
||||
uint64_group_code.set(groupId)
|
||||
@ -147,7 +179,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
|
||||
var troopList = service.allTroopList
|
||||
if(refresh || !service.isTroopCacheInited || troopList == null) {
|
||||
if(!requestGroupList(service)) {
|
||||
if(!requestGroupInfo(service)) {
|
||||
return Result.failure(Exception("获取群列表失败"))
|
||||
} else {
|
||||
troopList = service.allTroopList
|
||||
@ -160,15 +192,14 @@ internal object GroupSvc: BaseSvc() {
|
||||
val service = app
|
||||
.getRuntimeService(ITroopInfoService::class.java, "all")
|
||||
|
||||
var groupInfo = getGroupInfo(groupId)
|
||||
val groupInfo = getGroupInfo(groupId)
|
||||
|
||||
if(refresh || !service.isTroopCacheInited || groupInfo.troopuin.isNullOrBlank()) {
|
||||
groupInfo = requestGroupList(service, groupId.toLong()).onFailure {
|
||||
return Result.failure(it)
|
||||
}.getOrThrow()
|
||||
return if(refresh || !service.isTroopCacheInited || groupInfo.troopuin.isNullOrBlank()) {
|
||||
requestGroupInfo(service, groupId.toLong())
|
||||
} else {
|
||||
Result.success(groupInfo)
|
||||
}
|
||||
|
||||
return Result.success(groupInfo)
|
||||
}
|
||||
|
||||
suspend fun setGroupUniqueTitle(groupId: String, userId: String, title: String) {
|
||||
@ -177,7 +208,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
req.uint64_group_code.set(groupId.toLong())
|
||||
val memberInfo = Oidb_0x8fc.MemberInfo()
|
||||
memberInfo.uint64_uin.set(userId.toLong())
|
||||
memberInfo.bytes_uin_name.set(ByteStringMicro.copyFromUtf8(localMemberInfo.troopnick.ifBlank {
|
||||
memberInfo.bytes_uin_name.set(ByteStringMicro.copyFromUtf8(localMemberInfo.troopnick.ifEmpty {
|
||||
localMemberInfo.troopremark.ifNullOrEmpty("")
|
||||
}))
|
||||
memberInfo.bytes_special_title.set(ByteStringMicro.copyFromUtf8(title))
|
||||
@ -420,6 +451,36 @@ internal object GroupSvc: BaseSvc() {
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (info != null && (info.alias == null || info.alias.isBlank())) {
|
||||
val req = group_member_info.ReqBody()
|
||||
req.uint64_group_code.set(groupId.toLong())
|
||||
req.uint64_uin.set(uin.toLong())
|
||||
req.bool_new_client.set(true)
|
||||
req.uint32_client_type.set(1)
|
||||
req.uint32_rich_card_name_ver.set(1)
|
||||
val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray())
|
||||
if (respBuffer != null) {
|
||||
val rsp = group_member_info.RspBody()
|
||||
rsp.mergeFrom(respBuffer.slice(4))
|
||||
if (rsp.msg_meminfo.str_location.has()) {
|
||||
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
|
||||
}
|
||||
if (rsp.msg_meminfo.uint32_age.has()) {
|
||||
info.age = rsp.msg_meminfo.uint32_age.get().toByte()
|
||||
}
|
||||
if (rsp.msg_meminfo.bytes_group_honor.has()) {
|
||||
val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray()
|
||||
val honor = troop_honor.GroupUserCardHonor()
|
||||
honor.mergeFrom(honorBytes)
|
||||
info.level = honor.level.get()
|
||||
// 10315: medal_id not real group level
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
LogCenter.log(err.stackTraceToString(), Level.WARN)
|
||||
}
|
||||
return if (info != null) {
|
||||
Result.success(info)
|
||||
} else {
|
||||
@ -499,7 +560,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestGroupList(
|
||||
private suspend fun requestGroupInfo(
|
||||
service: ITroopInfoService
|
||||
): Boolean {
|
||||
refreshTroopList()
|
||||
@ -526,7 +587,7 @@ internal object GroupSvc: BaseSvc() {
|
||||
throw RuntimeException("AppRuntime cannot cast to AppInterface")
|
||||
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_LIST_HANDLER)
|
||||
|
||||
// void C(boolean foreRefresh, String groupId, String troopcode, int reqType); // RequestedTroopList/refreshMemberListFromServer
|
||||
// void C(boolean forceRefresh, String groupId, String troopcode, int reqType); // RequestedTroopList/refreshMemberListFromServer
|
||||
if (!GroupSvc::METHOD_REQ_TROOP_MEM_LIST.isInitialized) {
|
||||
METHOD_REQ_TROOP_MEM_LIST = businessHandler.javaClass.declaredMethods.first {
|
||||
it.parameterCount == 4
|
||||
@ -592,9 +653,9 @@ internal object GroupSvc: BaseSvc() {
|
||||
METHOD_REQ_MEMBER_INFO_V2.invoke(businessHandler, groupId.toString(), groupUin2GroupCode(groupId).toString(), arrayListOf(memberUin.toString()))
|
||||
}
|
||||
|
||||
private suspend fun requestGroupList(dataService: ITroopInfoService, uin: Long): Result<TroopInfo> {
|
||||
private suspend fun requestGroupInfo(dataService: ITroopInfoService, uin: Long): Result<TroopInfo> {
|
||||
val strUin = uin.toString()
|
||||
val list = withTimeoutOrNull(5000) {
|
||||
val info = withTimeoutOrNull(5000) {
|
||||
var troopInfo: TroopInfo?
|
||||
do {
|
||||
troopInfo = dataService.getTroopInfo(strUin)
|
||||
@ -602,8 +663,8 @@ internal object GroupSvc: BaseSvc() {
|
||||
} while (troopInfo == null || troopInfo.troopuin.isNullOrBlank())
|
||||
return@withTimeoutOrNull troopInfo
|
||||
}
|
||||
return if (list != null) {
|
||||
Result.success(list)
|
||||
return if (info != null) {
|
||||
Result.success(info)
|
||||
} else {
|
||||
Result.failure(Exception("获取群列表失败"))
|
||||
}
|
||||
@ -805,12 +866,13 @@ internal object GroupSvc: BaseSvc() {
|
||||
senderId = obj["u"].asLong,
|
||||
publishTime = obj["pubt"].asLong,
|
||||
message = GroupAnnouncementMessage(
|
||||
text = obj["msg"].asJsonObject["text"].asString,
|
||||
images = obj["msg"].asJsonObject["pics"].asJsonArrayOrNull?.map {
|
||||
// text = obj["msg"].asJsonObject["text"].asString,
|
||||
text = fromHtml(obj["msg"].asJsonObject["text"].asString),
|
||||
images = obj["msg"].asJsonObject["pics"].asJsonArrayOrNull?.map { pic ->
|
||||
GroupAnnouncementMessageImage(
|
||||
id = it.jsonObject["id"].asString,
|
||||
width = it.jsonObject["w"].asString,
|
||||
height = it.jsonObject["h"].asString,
|
||||
id = pic.jsonObject["id"].asString,
|
||||
width = pic.jsonObject["w"].asString,
|
||||
height = pic.jsonObject["h"].asString,
|
||||
)
|
||||
} ?: ArrayList()
|
||||
)
|
||||
@ -821,6 +883,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)
|
||||
suspend fun uploadImageTroopNotice(image: String): Result<GroupAnnouncementMessageImage> {
|
||||
val file = FileUtils.parseAndSave(image)
|
||||
@ -907,4 +977,27 @@ internal object GroupSvc: BaseSvc() {
|
||||
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(" ")}")
|
||||
}
|
||||
}
|
||||
}
|
@ -31,10 +31,6 @@ import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
internal object MsgSvc: BaseSvc() {
|
||||
fun uploadForwardMsg(): Result<String> {
|
||||
return Result.failure(Exception("Not implemented"))
|
||||
}
|
||||
|
||||
suspend fun prepareTempChatFromGroup(
|
||||
groupId: String,
|
||||
peerId: String
|
||||
@ -176,9 +172,6 @@ internal object MsgSvc: BaseSvc() {
|
||||
fromId: String = peedId,
|
||||
retryCnt: Int = 3
|
||||
): Result<Pair<Long, Int>> {
|
||||
//LogCenter.log(message.toString(), Level.ERROR)
|
||||
//callback.msgHash = result.second 什么垃圾代码,万一cb比你快,你不就寄了?
|
||||
|
||||
// 主动临时消息
|
||||
when (chatType) {
|
||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||
@ -188,13 +181,7 @@ internal object MsgSvc: BaseSvc() {
|
||||
}
|
||||
}
|
||||
}
|
||||
val result = MessageHelper.sendMessageWithoutMsgId(
|
||||
chatType,
|
||||
peedId,
|
||||
message,
|
||||
fromId,
|
||||
MessageCallback(peedId, 0)
|
||||
)
|
||||
val result = MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0))
|
||||
return if (result.isFailure
|
||||
&& result.exceptionOrNull()?.javaClass == SendMsgException::class.java
|
||||
&& 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(
|
||||
@SerialName("user_id") val memberUin: Long,
|
||||
@SerialName("time") val shutuptimestap: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class GroupAtAllRemainInfo(
|
||||
@SerialName("can_at_all") val canAtAll: Boolean,
|
||||
@SerialName("remain_at_all_count_for_group") val remainAtAllCountForGroup: Int,
|
||||
@SerialName("remain_at_all_count_for_uin") val remainAtAllCountForUin: Int
|
||||
)
|
@ -274,7 +274,7 @@ internal object MessageMaker {
|
||||
element.elementType = MsgConstant.KELEMTYPEREPLY
|
||||
val reply = ReplyElement()
|
||||
|
||||
val msgHash = data["id"].asString.toInt()
|
||||
val msgHash = data["id"].asInt
|
||||
val mapping = MessageHelper.getMsgMappingByHash(msgHash)
|
||||
?: return Result.failure(Exception("不存在该消息映射,无法回复消息"))
|
||||
|
||||
@ -627,10 +627,16 @@ internal object MessageMaker {
|
||||
else -> {
|
||||
val info = GroupSvc.getTroopMemberInfoByUin(peerId, qq, true).onFailure {
|
||||
LogCenter.log("无法获取群成员信息: $qq", Level.ERROR)
|
||||
}.getOrThrow()
|
||||
at.content = "@${info.troopnick
|
||||
.ifNullOrEmpty(info.friendnick)
|
||||
.ifNullOrEmpty(qq)}"
|
||||
}.getOrNull()
|
||||
if (info != null) {
|
||||
at.content = "@${
|
||||
info.troopnick
|
||||
.ifNullOrEmpty(info.friendnick)
|
||||
.ifNullOrEmpty(qq)
|
||||
}"
|
||||
} else {
|
||||
at.content = "@${data["name"].asStringOrNull.ifNullOrEmpty(qq)}"
|
||||
}
|
||||
at.atType = MsgConstant.ATTYPEONE
|
||||
at.atNtUid = ContactHelper.getUidByUinAsync(qq.toLong())
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ internal object MessageConvert {
|
||||
MsgConstant.KELEMTYPEREPLY to MessageElemConverter.ReplyConverter,
|
||||
MsgConstant.KELEMTYPEGRAYTIP to MessageElemConverter.GrayTipsConverter,
|
||||
MsgConstant.KELEMTYPEFILE to MessageElemConverter.FileConverter,
|
||||
MsgConstant.KELEMTYPEMARKDOWN to MessageElemConverter.MarkdownConverter,
|
||||
//MsgConstant.KELEMTYPEMULTIFORWARD to MessageElemConverter.XmlMultiMsgConverter,
|
||||
//MsgConstant.KELEMTYPESTRUCTLONGMSG to MessageElemConverter.XmlLongMsgConverter,
|
||||
)
|
||||
|
@ -324,14 +324,15 @@ internal sealed class MessageElemConverter: IMessageConvert {
|
||||
val notify = tip.jsonGrayTipElement
|
||||
when(notify.busiId) {
|
||||
/* 新人入群 */ 17L, /* 群戳一戳 */1061L,
|
||||
/* 群撤回 */1014L, /* 群设精消息 */2401L -> {}
|
||||
/* 群撤回 */1014L, /* 群设精消息 */2401L,
|
||||
/* 群头衔 */2407L -> {}
|
||||
else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN)
|
||||
}
|
||||
}
|
||||
MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
|
||||
val notify = tip.xmlElement
|
||||
when(notify.busiId) {
|
||||
/* 群戳一戳 */1061L -> {}
|
||||
/* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
|
||||
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) {
|
||||
throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import kotlin.coroutines.resume
|
||||
internal object ContactHelper {
|
||||
suspend fun getUinByUidAsync(uid: String): String {
|
||||
if (uid.isBlank() || uid == "0") {
|
||||
return "0"
|
||||
return "-1"
|
||||
}
|
||||
|
||||
val kernelService = NTServiceFetcher.kernelService
|
||||
@ -20,7 +20,7 @@ internal object ContactHelper {
|
||||
sessionService.uixConvertService.getUin(hashSetOf(uid)) {
|
||||
continuation.resume(it)
|
||||
}
|
||||
}[uid]?.toString() ?: "0"
|
||||
}[uid]?.toString() ?: "-1"
|
||||
}
|
||||
|
||||
suspend fun getUidByUinAsync(peerId: Long): String {
|
||||
|
@ -9,6 +9,7 @@ import com.tencent.qqnt.msg.api.IMsgService
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
@ -38,7 +39,11 @@ internal object MessageHelper {
|
||||
): Pair<Long, Int> {
|
||||
val uniseq = generateMsgId(chatType)
|
||||
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, decodeCQCode(message)).also {
|
||||
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
|
||||
if (it.second.isEmpty() && !it.first) {
|
||||
error("消息合成失败,请查看日志或者检查输入。")
|
||||
} else if (it.second.isEmpty()) {
|
||||
return System.currentTimeMillis() to 0
|
||||
}
|
||||
}.second.filter {
|
||||
it.elementType != -1
|
||||
} as ArrayList<MsgElement>
|
||||
@ -59,6 +64,12 @@ internal object MessageHelper {
|
||||
}.second.filter {
|
||||
it.elementType != -1
|
||||
} as ArrayList<MsgElement>
|
||||
|
||||
// ActionMsg No Care
|
||||
if (msg.isEmpty()) {
|
||||
return Result.success(System.currentTimeMillis() to 0)
|
||||
}
|
||||
|
||||
val totalSize = msg.filter {
|
||||
it.elementType == MsgConstant.KELEMTYPEPIC ||
|
||||
it.elementType == MsgConstant.KELEMTYPEPTT ||
|
||||
@ -67,11 +78,11 @@ internal object MessageHelper {
|
||||
(it.picElement?.fileSize ?: 0) + (it.pttElement?.fileSize
|
||||
?: 0) + (it.videoElement?.fileSize ?: 0)
|
||||
}.reduceOrNull { a, b -> a + b } ?: 0
|
||||
val estimateTime = (totalSize / (300 * 1024)) * 1000 + 2000
|
||||
|
||||
val estimateTime = (totalSize / (300 * 1024)) * 1000 + 5000
|
||||
lateinit var sendResultPair: Pair<Long, Int>
|
||||
val sendRet = withTimeoutOrNull<Pair<Int, String>>(estimateTime) {
|
||||
suspendCoroutine {
|
||||
suspendCancellableCoroutine {
|
||||
GlobalScope.launch {
|
||||
sendResultPair = sendMessageWithoutMsgId(
|
||||
chatType,
|
||||
|
@ -64,6 +64,7 @@ internal object HTTPServer {
|
||||
guildAction()
|
||||
testAction()
|
||||
requestRouter()
|
||||
fav()
|
||||
if (ShamrockConfig.isDev()) {
|
||||
qsign()
|
||||
obtainProtocolData()
|
||||
|
@ -23,7 +23,7 @@ internal object ActionManager {
|
||||
// UserActions
|
||||
GetProfileCard, GetFriendList, SendLike, GetUid, GetUinByUid, ScanQRCode, SetProfileCard,
|
||||
GetCookies, GetCSRF, GetCredentials, RestartMe, CleanCache, GetModelShow, SetModelShow,
|
||||
GetModelShowList, GetOnlineClients, GetStrangerInfo, IsBlackListUin, GetHttpCookies,
|
||||
GetModelShowList, GetOnlineClients, GetStrangerInfo, IsBlackListUin, GetHttpCookies, GetFriendSystemMsg,
|
||||
|
||||
// GroupInfo
|
||||
GetTroopList, GetTroopInfo, GetTroopList, GetTroopMemberInfo, GetTroopMemberList,
|
||||
@ -31,7 +31,8 @@ internal object ActionManager {
|
||||
// GroupActions
|
||||
ModifyTroopName, LeaveTroop, KickTroopMember, BanTroopMember, SetGroupWholeBan, SetGroupAdmin,
|
||||
ModifyTroopMemberName, SetGroupUnique, GetTroopHonor, GroupPoke, SetEssenceMessage, DeleteEssenceMessage,
|
||||
GetGroupSystemMsg, GetProhibitedMemberList, GetEssenceMessageList, GetGroupNotice, SendGroupNotice,
|
||||
GetGroupSystemMsg, GetProhibitedMemberList, GetEssenceMessageList, GetGroupNotice, SendGroupNotice, SendGroupSign,
|
||||
GetGroupRemainAtAllRemain,
|
||||
|
||||
// MSG ACTIONS
|
||||
SendMessage, DeleteMessage, GetMsg, GetForwardMsg, SendPrivateForwardMessage, SendGroupMessage, SendPrivateMessage,
|
||||
@ -42,17 +43,20 @@ internal object ActionManager {
|
||||
DeleteGroupFile, GetGroupFileSystemInfo, GetGroupRootFiles, GetGroupSubFiles,
|
||||
GetGroupFileUrl, UploadPrivateFile,
|
||||
|
||||
//REQUEST ACTION
|
||||
// REQUEST ACTION
|
||||
SetFriendAddRequest, SetGroupAddRequest,
|
||||
|
||||
// GUILD
|
||||
GetGuildServiceProfile,
|
||||
GetGuildServiceProfile, GetGuildList,
|
||||
|
||||
// WEATHER
|
||||
GetWeatherCityCode, GetWeather,
|
||||
|
||||
// FAV
|
||||
FavAddTextMsg, FavAddImageMsg, FavGetItemContent, FavGetItemList,
|
||||
|
||||
// OTHER
|
||||
GetDeviceBattery, DownloadFile
|
||||
GetDeviceBattery, DownloadFile, QuickOperation
|
||||
).forEach {
|
||||
it.alias.forEach { name ->
|
||||
actionMap[name] = it
|
||||
|
@ -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
|
||||
)
|
||||
}
|
@ -37,9 +37,9 @@ internal object GetForwardMsg: IActionHandler() {
|
||||
realId = msg.msgSeq.toInt(),
|
||||
sender = MessageSender(
|
||||
msg.senderUin, msg.sendNickName
|
||||
.ifBlank { msg.sendMemberName }
|
||||
.ifBlank { msg.sendRemarkName }
|
||||
.ifBlank { msg.peerName }, "unknown", 0, msg.senderUid
|
||||
.ifEmpty { msg.sendMemberName }
|
||||
.ifEmpty { msg.sendRemarkName }
|
||||
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid
|
||||
),
|
||||
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
|
||||
it.toJson()
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
override val requiredParams: Array<String> = arrayOf("group_id", "folder_id")
|
||||
|
||||
override fun path(): String = "get_group_files_by_folder"
|
||||
override fun path(): String = "get_group_system_msg"
|
||||
}
|
@ -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 moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
|
||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||
import mqq.app.MobileQQ
|
||||
|
||||
internal object GetGuildServiceProfile: IActionHandler() {
|
||||
internal object GetGuildServiceProfile : IActionHandler() {
|
||||
override suspend fun internalHandle(session: ActionSession): String {
|
||||
TODO("Not yet implemented")
|
||||
return invoke(echo = session.echo)
|
||||
}
|
||||
|
||||
operator fun invoke(echo: JsonElement = EmptyJsonString): String {
|
||||
// TODO: get_guild_service_profile
|
||||
return ok(EmptyJsonObject, echo, "此功能尚未实现")
|
||||
|
||||
val service = AppRuntimeFetcher.appRuntime
|
||||
.getRuntimeService(IGPSService::class.java, "all")
|
||||
if (!service.isGProSDKInitCompleted) {
|
||||
|
@ -85,9 +85,9 @@ internal object GetHistoryMsg: IActionHandler() {
|
||||
realId = seq,
|
||||
sender = MessageSender(
|
||||
msg.senderUin, msg.sendNickName
|
||||
.ifBlank { msg.sendMemberName }
|
||||
.ifBlank { msg.sendRemarkName }
|
||||
.ifBlank { msg.peerName }, "unknown", 0, msg.senderUid
|
||||
.ifEmpty { msg.sendMemberName }
|
||||
.ifEmpty { msg.sendRemarkName }
|
||||
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid
|
||||
),
|
||||
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
|
||||
it.toJson()
|
||||
|
@ -30,9 +30,9 @@ internal object GetMsg: IActionHandler() {
|
||||
realId = seq,
|
||||
sender = MessageSender(
|
||||
msg.senderUin, msg.sendNickName
|
||||
.ifBlank { msg.sendMemberName }
|
||||
.ifBlank { msg.sendRemarkName }
|
||||
.ifBlank { msg.peerName }, "unknown", 0, msg.senderUid
|
||||
.ifEmpty { msg.sendMemberName }
|
||||
.ifEmpty { msg.sendRemarkName }
|
||||
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid
|
||||
),
|
||||
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
|
||||
it.toJson()
|
||||
|
@ -29,7 +29,7 @@ internal object GetProfileCard: IActionHandler() {
|
||||
uin = card.uin.toLong(),
|
||||
name = card.strNick,
|
||||
mail = card.strShowName ?: card.strEmail ?: "",
|
||||
remark = card.strReMark.let { if (it.isNullOrBlank()) card.strAutoRemark else it },
|
||||
remark = card.strReMark.let { if (it.isNullOrEmpty()) card.strAutoRemark else it },
|
||||
findMethod = card.addSrcName,
|
||||
displayName = card.strContactName,
|
||||
maxVoteCnt = card.bAvailVoteCnt,
|
||||
|
@ -30,7 +30,7 @@ internal object GetTroopHonor: IActionHandler() {
|
||||
GroupSvc.parseHonor(member.honorList).forEach {
|
||||
val honor = nativeDecodeHonor(member.memberuin, it, member.mHonorRichFlag)
|
||||
if (honor != null) {
|
||||
honor.nick = member.troopnick.ifBlank { member.friendnick }
|
||||
honor.nick = member.troopnick.ifEmpty { member.friendnick }
|
||||
honorInfo.add(honor)
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,9 @@ internal object GetTroopMemberInfo : IActionHandler() {
|
||||
unfriendly = false,
|
||||
title = info.mUniqueTitle ?: "",
|
||||
titleExpireTime = info.mUniqueTitleExpire,
|
||||
cardChangeable = GroupSvc.isAdmin(groupId)
|
||||
cardChangeable = GroupSvc.isAdmin(groupId),
|
||||
age = info.age.toInt(),
|
||||
shutUpTimestamp = 0L
|
||||
), echo
|
||||
)
|
||||
}
|
||||
|
@ -25,7 +25,9 @@ internal object GetTroopMemberList : IActionHandler() {
|
||||
val memberList = GroupSvc.getGroupMemberList(groupId, refresh).onFailure {
|
||||
return error(it.message ?: "unknown error", echo, arrayResult = true)
|
||||
}.getOrThrow()
|
||||
|
||||
val prohibitedMemberList = GroupSvc.getProhibitedMemberList(groupId.toLong())
|
||||
.getOrDefault(arrayListOf())
|
||||
.associate { it.memberUin to it.shutuptimestap.toLong() }
|
||||
return ok(arrayListOf<SimpleTroopMemberInfo>().apply {
|
||||
memberList.forEach { info ->
|
||||
if (info.memberuin != "0") {
|
||||
@ -59,7 +61,9 @@ internal object GetTroopMemberList : IActionHandler() {
|
||||
unfriendly = false,
|
||||
title = info.mUniqueTitle ?: "",
|
||||
titleExpireTime = info.mUniqueTitleExpire,
|
||||
cardChangeable = GroupSvc.isAdmin(groupId)
|
||||
cardChangeable = GroupSvc.isAdmin(groupId),
|
||||
age = 0,
|
||||
shutUpTimestamp = prohibitedMemberList[info.memberuin.toLong()] ?: 0L
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -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.MultiMsgInfo
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.*
|
||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||
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.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() {
|
||||
override suspend fun internalHandle(session: ActionSession): String {
|
||||
val detailType = session.getStringOrNull("detail_type") ?: session.getStringOrNull("message_type")
|
||||
@ -39,31 +24,33 @@ internal object SendForwardMessage : IActionHandler() {
|
||||
MessageHelper.obtainMessageTypeByDetailType(it)
|
||||
} ?: run {
|
||||
if (session.has("user_id")) {
|
||||
MsgConstant.KCHATTYPEC2C
|
||||
if (session.has("group_id")) {
|
||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
} else {
|
||||
MsgConstant.KCHATTYPEC2C
|
||||
}
|
||||
} else if (session.has("group_id")) {
|
||||
MsgConstant.KCHATTYPEGROUP
|
||||
} else {
|
||||
return noParam("detail_type/message_type", session.echo)
|
||||
}
|
||||
}
|
||||
val peerId = when (chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam(
|
||||
"group_id",
|
||||
session.echo
|
||||
)
|
||||
|
||||
MsgConstant.KCHATTYPEC2C -> session.getStringOrNull("user_id") ?: return noParam(
|
||||
"user_id",
|
||||
session.echo
|
||||
)
|
||||
|
||||
val peerId = when(chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
|
||||
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo)
|
||||
else -> error("unknown chat type: $chatType")
|
||||
}
|
||||
if (session.isArray("messages")) {
|
||||
val messages = session.getArray("messages")
|
||||
invoke(chatType, peerId, messages, echo = session.echo)
|
||||
val fromId = when(chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
|
||||
MsgConstant.KCHATTYPEC2C -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo)
|
||||
else -> error("unknown chat type: $chatType")
|
||||
}
|
||||
return if (session.isArray("messages")) {
|
||||
val messages = session.getArray("messages")
|
||||
invoke(chatType, peerId, messages, fromId, echo = session.echo)
|
||||
} else {
|
||||
logic("未知格式合并转发消息", session.echo)
|
||||
}
|
||||
return logic("未知格式合并转发消息", session.echo)
|
||||
} catch (e: ParamsException) {
|
||||
return noParam(e.message!!, session.echo)
|
||||
} catch (e: Throwable) {
|
||||
@ -74,7 +61,8 @@ internal object SendForwardMessage : IActionHandler() {
|
||||
suspend operator fun invoke(
|
||||
chatType: Int,
|
||||
peerId: String,
|
||||
message: JsonArray,
|
||||
messages: JsonArray,
|
||||
fromId: String = peerId,
|
||||
echo: JsonElement = EmptyJsonString
|
||||
): String {
|
||||
kotlin.runCatching {
|
||||
@ -83,63 +71,91 @@ internal object SendForwardMessage : IActionHandler() {
|
||||
val msgService = sessionService.msgService
|
||||
val selfUin = TicketSvc.getUin()
|
||||
|
||||
val nodes = message.map {
|
||||
if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段
|
||||
it.asJsonObject["data"].asJsonObject.let { data ->
|
||||
if (data.containsKey("content")) {
|
||||
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)
|
||||
val multiNodes = messages.map {
|
||||
if (it.asJsonObject["type"].asStringOrNull != "node") {
|
||||
LogCenter.log("包含非node类型节点", Level.WARN)
|
||||
return@map null
|
||||
}
|
||||
}.map {
|
||||
if (it is ForwardMsgNode.MessageIdNode) {
|
||||
val recordResult = MsgSvc.getMsg(it.id)
|
||||
if (!recordResult.isFailure) {
|
||||
ForwardMsgNode.EmptyNode
|
||||
} else {
|
||||
val record = recordResult.getOrThrow()
|
||||
ForwardMsgNode.MessageNode(
|
||||
name = record.peerName,
|
||||
content = record.toSegments().map { segment ->
|
||||
if (it.asJsonObject["data"] !is JsonObject) {
|
||||
LogCenter.log("data字段错误", Level.WARN)
|
||||
return@map null
|
||||
}
|
||||
it.asJsonObject["data"].asJsonObject.let { data ->
|
||||
if (data.containsKey("id")) {
|
||||
val record = MsgSvc.getMsg(data["id"].asInt).getOrNull()
|
||||
if (record == null) {
|
||||
LogCenter.log("合并转发消息节点消息获取失败:${data["id"]}", Level.WARN)
|
||||
return@map null
|
||||
} else {
|
||||
record.peerName to record.toSegments().map { segment ->
|
||||
segment.toJson()
|
||||
}.json
|
||||
)
|
||||
}
|
||||
} else if (data.containsKey("content")) {
|
||||
(data["name"].asStringOrNull ?: "Anno") to when (val raw = data["content"]) {
|
||||
is JsonObject -> raw.asJsonArray
|
||||
is JsonArray -> raw.asJsonArray
|
||||
else -> MessageHelper.decodeCQCode(raw.asString)
|
||||
}
|
||||
} else {
|
||||
LogCenter.log("消息节点缺少id或content字段", Level.WARN)
|
||||
return@map null
|
||||
}
|
||||
} else {
|
||||
it as ForwardMsgNode.MessageNode
|
||||
}
|
||||
}.filter {
|
||||
it.content != null
|
||||
}.map { node ->
|
||||
val result = MessageHelper.sendMessageNoCb(MsgConstant.KCHATTYPEC2C, selfUin, node.content.let { msg ->
|
||||
when (msg) {
|
||||
is JsonArray -> msg
|
||||
is JsonObject -> listOf(msg).jsonArray
|
||||
else -> MessageHelper.decodeCQCode(msg.asString)
|
||||
}.let { node ->
|
||||
val content = node.second.map { msg ->
|
||||
when (msg.asJsonObject["type"].asStringOrNull ?: "text") {
|
||||
"at" -> {
|
||||
buildJsonObject {
|
||||
put("type", "text")
|
||||
putJsonObject("data") {
|
||||
put(
|
||||
"text", "@${
|
||||
msg.asJsonObject["data"].asJsonObject["name"].asStringOrNull.ifNullOrEmpty(
|
||||
msg.asJsonObject["data"].asJsonObject["qq"].asString
|
||||
)
|
||||
}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"voice" -> {
|
||||
buildJsonObject {
|
||||
put("type", "text")
|
||||
putJsonObject("data") {
|
||||
put("text", "[语音]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"node" -> {
|
||||
LogCenter.log("合并转发消息暂时不支持嵌套", Level.WARN)
|
||||
buildJsonObject {
|
||||
put("type", "text")
|
||||
putJsonObject("data") {
|
||||
put("text", "[合并转发消息]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> msg
|
||||
}
|
||||
}.json
|
||||
|
||||
val result = MessageHelper.sendMessageNoCb(MsgConstant.KCHATTYPEC2C, selfUin, content)
|
||||
if (result.first != 0) {
|
||||
LogCenter.log("合并转发消息节点消息发送失败", Level.WARN)
|
||||
}
|
||||
})
|
||||
if (result.first != 0) {
|
||||
LogCenter.log("合并转发消息节点消息发送失败", Level.WARN)
|
||||
result.second to node.first
|
||||
}
|
||||
return@map result.second
|
||||
}
|
||||
}.filterNotNull()
|
||||
|
||||
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)
|
||||
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))
|
||||
|
||||
return ok(
|
||||
@ -154,7 +170,7 @@ internal object SendForwardMessage : IActionHandler() {
|
||||
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"
|
||||
}
|
@ -9,7 +9,7 @@ internal object SendGroupForwardMessage: IActionHandler() {
|
||||
val groupId = session.getString("group_id")
|
||||
return if (session.isArray("messages")) {
|
||||
val messages = session.getArray("messages")
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId, messages, session.echo)
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId, messages, echo = session.echo)
|
||||
} else {
|
||||
logic("未知格式合并转发消息", session.echo)
|
||||
}
|
||||
|
@ -8,16 +8,18 @@ import moe.fuqiuluo.shamrock.tools.jsonArray
|
||||
internal object SendGroupMessage: IActionHandler() {
|
||||
override suspend fun internalHandle(session: ActionSession): String {
|
||||
val groupId = session.getString("group_id")
|
||||
val retryCnt = session.getIntOrNull("retry_cnt")
|
||||
val recallDuration = session.getLongOrNull("recall_duration")
|
||||
return if (session.isString("message")) {
|
||||
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
||||
val message = session.getString("message")
|
||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, autoEscape, echo = session.echo)
|
||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, autoEscape, echo = session.echo, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
|
||||
} else if (session.isObject("message")) {
|
||||
val message = session.getObject("message")
|
||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, listOf( message ).jsonArray, session.echo)
|
||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, listOf( message ).jsonArray, session.echo, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
|
||||
} else {
|
||||
val message = session.getArray("message")
|
||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, session.echo)
|
||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, session.echo, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||
@ -20,11 +24,15 @@ internal object SendMessage: IActionHandler() {
|
||||
override suspend fun internalHandle(session: ActionSession): String {
|
||||
val detailType = session.getStringOrNull("detail_type") ?: session.getStringOrNull("message_type")
|
||||
try {
|
||||
var chatType = detailType?.let {
|
||||
val chatType = detailType?.let {
|
||||
MessageHelper.obtainMessageTypeByDetailType(it)
|
||||
} ?: run {
|
||||
if (session.has("user_id")) {
|
||||
MsgConstant.KCHATTYPEC2C
|
||||
if (session.has("group_id")) {
|
||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
} else {
|
||||
MsgConstant.KCHATTYPEC2C
|
||||
}
|
||||
} else if (session.has("group_id")) {
|
||||
MsgConstant.KCHATTYPEGROUP
|
||||
} else {
|
||||
@ -33,27 +41,26 @@ internal object SendMessage: IActionHandler() {
|
||||
}
|
||||
val peerId = when(chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
|
||||
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo)
|
||||
else -> error("unknown chat type: $chatType")
|
||||
}
|
||||
val fromId = when(chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
|
||||
MsgConstant.KCHATTYPEC2C -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo)
|
||||
else -> error("unknown chat type: $chatType")
|
||||
}
|
||||
var fromId = peerId
|
||||
if (chatType == MsgConstant.KCHATTYPEC2C) {
|
||||
val groupId = session.getStringOrNull("group_id")
|
||||
if (groupId != null) {
|
||||
chatType = MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
fromId = groupId
|
||||
}
|
||||
}
|
||||
val retryCnt = session.getIntOrNull("retry_cnt")
|
||||
val recallDuration = session.getLongOrNull("recall_duration")
|
||||
return if (session.isString("message")) {
|
||||
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
||||
val message = session.getString("message")
|
||||
invoke(chatType, peerId, message, autoEscape, echo = session.echo, fromId = fromId)
|
||||
invoke(chatType, peerId, message, autoEscape, echo = session.echo, fromId = fromId, retryCnt = retryCnt ?: 3, recallDuration = recallDuration)
|
||||
} else if (session.isArray("message")) {
|
||||
val message = session.getArray("message")
|
||||
invoke(chatType, peerId, message, session.echo, fromId = fromId)
|
||||
invoke(chatType, peerId, message, session.echo, fromId = fromId, retryCnt ?: 3, recallDuration = recallDuration)
|
||||
} else {
|
||||
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, recallDuration = recallDuration)
|
||||
}
|
||||
} catch (e: ParamsException) {
|
||||
return noParam(e.message!!, session.echo)
|
||||
@ -69,6 +76,8 @@ internal object SendMessage: IActionHandler() {
|
||||
message: String,
|
||||
autoEscape: Boolean,
|
||||
fromId: String = peerId,
|
||||
retryCnt: Int,
|
||||
recallDuration: Long?,
|
||||
echo: JsonElement = EmptyJsonString
|
||||
): String {
|
||||
//if (!ContactHelper.checkContactAvailable(chatType, peerId)) {
|
||||
@ -89,7 +98,7 @@ internal object SendMessage: IActionHandler() {
|
||||
LogCenter.log("CQ码不合法", Level.WARN)
|
||||
return logic("CQCode is illegal", echo)
|
||||
} else {
|
||||
MsgSvc.sendToAio(chatType, peerId, msg, fromId = fromId)
|
||||
MsgSvc.sendToAio(chatType, peerId, msg, fromId = fromId, retryCnt)
|
||||
}
|
||||
}
|
||||
if (result.isFailure) {
|
||||
@ -99,6 +108,7 @@ internal object SendMessage: IActionHandler() {
|
||||
if (pair.first <= 0) {
|
||||
return logic("send message failed", echo = echo)
|
||||
}
|
||||
recallDuration?.let { autoRecall(pair.second, it) }
|
||||
return ok(MessageResult(
|
||||
msgId = pair.second,
|
||||
time = (pair.first * 0.001).toLong()
|
||||
@ -107,12 +117,12 @@ internal object SendMessage: IActionHandler() {
|
||||
|
||||
// 消息段格式消息
|
||||
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, recallDuration: Long?,
|
||||
): String {
|
||||
//if (!ContactHelper.checkContactAvailable(chatType, peerId)) {
|
||||
// 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) {
|
||||
return logic(result.exceptionOrNull()?.message ?: "", echo)
|
||||
}
|
||||
@ -120,12 +130,20 @@ internal object SendMessage: IActionHandler() {
|
||||
if (pair.first <= 0) {
|
||||
return logic("send message failed", echo = echo)
|
||||
}
|
||||
recallDuration?.let { autoRecall(pair.second, it) }
|
||||
return ok(MessageResult(
|
||||
msgId = pair.second,
|
||||
time = (pair.first * 0.001).toLong()
|
||||
), echo)
|
||||
}
|
||||
|
||||
private fun autoRecall(msgHash: Int, duration: Long) {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
delay(duration)
|
||||
MsgSvc.recallMsg(msgHash)
|
||||
}
|
||||
}
|
||||
|
||||
override val requiredParams: Array<String> = arrayOf("message")
|
||||
|
||||
override fun path(): String = "send_message"
|
||||
|
@ -9,7 +9,7 @@ internal object SendPrivateForwardMessage : IActionHandler() {
|
||||
val userId = session.getString("user_id")
|
||||
return if (session.isArray("messages")) {
|
||||
val messages = session.getArray("messages")
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId, messages, session.echo)
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId, messages, echo = session.echo)
|
||||
} else {
|
||||
logic("未知格式合并转发消息", session.echo)
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ internal object SendPrivateMessage: IActionHandler() {
|
||||
val userId = session.getString("user_id")
|
||||
val groupId = session.getStringOrNull("group_id")
|
||||
val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
val retryCnt = session.getIntOrNull("retry_cnt")
|
||||
val recallDuration = session.getLongOrNull("recall_duration")
|
||||
return if (session.isString("message")) {
|
||||
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
||||
val message = session.getString("message")
|
||||
@ -19,7 +21,9 @@ internal object SendPrivateMessage: IActionHandler() {
|
||||
message = message,
|
||||
autoEscape = autoEscape,
|
||||
echo = session.echo,
|
||||
fromId = groupId ?: userId
|
||||
fromId = groupId ?: userId,
|
||||
retryCnt = retryCnt ?: 3,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
} else if (session.isArray("message")) {
|
||||
val message = session.getArray("message")
|
||||
@ -28,7 +32,9 @@ internal object SendPrivateMessage: IActionHandler() {
|
||||
peerId = userId,
|
||||
message = message,
|
||||
echo = session.echo,
|
||||
fromId = groupId ?: userId
|
||||
fromId = groupId ?: userId,
|
||||
retryCnt = retryCnt ?: 3,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
} else {
|
||||
val message = session.getObject("message")
|
||||
@ -37,7 +43,9 @@ internal object SendPrivateMessage: IActionHandler() {
|
||||
peerId = userId,
|
||||
message = listOf( message ).jsonArray,
|
||||
echo = session.echo,
|
||||
fromId = groupId ?: userId
|
||||
fromId = groupId ?: userId,
|
||||
retryCnt = retryCnt ?: 3,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ internal object SetFriendAddRequest: IActionHandler() {
|
||||
if (ts.toString().length < 13) {
|
||||
// time but not seq, query seq again
|
||||
val reqs = FriendSvc.requestFriendSystemMsgNew(20, 0, 0, 1)
|
||||
val req = reqs?.first {
|
||||
val req = reqs?.firstOrNull {
|
||||
it.msg_time.get() == ts
|
||||
}
|
||||
// 好友请求seq貌似就是time*1000,查不到直接*1000
|
||||
|
@ -27,10 +27,10 @@ internal object SetGroupAddRequest: IActionHandler() {
|
||||
var reqs = GroupSvc.requestGroupSystemMsgNew(20, 1)
|
||||
val riskReqs = GroupSvc.requestGroupSystemMsgNew(20, 2)
|
||||
reqs = reqs + riskReqs
|
||||
val req = reqs.first {
|
||||
val req = reqs.firstOrNull {
|
||||
it.msg_time.get() == ts
|
||||
}
|
||||
ts = req.msg_seq?.get() ?: return error("失败:未找到该请求", echo)
|
||||
ts = req?.msg_seq?.get() ?: return error("失败:未找到该请求", echo)
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
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.get
|
||||
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.IsBlackListUin
|
||||
import moe.fuqiuluo.shamrock.tools.fetchGetOrThrow
|
||||
@ -30,4 +31,9 @@ fun Routing.friendAction() {
|
||||
val uin = fetchOrThrow("user_id")
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.request.httpVersion
|
||||
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.post
|
||||
import io.ktor.server.routing.route
|
||||
import io.ktor.util.pipeline.PipelineContext
|
||||
import kotlinx.serialization.Contextual
|
||||
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.action.ActionManager
|
||||
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.Status
|
||||
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.fetchOrThrow
|
||||
import moe.fuqiuluo.shamrock.tools.fetchPostJsonElement
|
||||
import moe.fuqiuluo.shamrock.tools.fetchPostJsonElementOrNull
|
||||
import moe.fuqiuluo.shamrock.tools.fetchPostJsonObject
|
||||
import moe.fuqiuluo.shamrock.tools.fetchPostJsonObjectOrNull
|
||||
import moe.fuqiuluo.shamrock.tools.isJsonArray
|
||||
import moe.fuqiuluo.shamrock.tools.isJsonData
|
||||
import moe.fuqiuluo.shamrock.tools.isJsonObject
|
||||
import moe.fuqiuluo.shamrock.tools.isJsonString
|
||||
import moe.fuqiuluo.shamrock.tools.json
|
||||
@ -39,6 +49,31 @@ data class OldApiResult<T>(
|
||||
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() {
|
||||
route("/") {
|
||||
get {
|
||||
@ -49,6 +84,15 @@ fun Routing.echoVersion() {
|
||||
)
|
||||
}
|
||||
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 echo = if (isJsonObject("echo") || isJsonArray("echo")) {
|
||||
fetchPostJsonElement("echo")
|
||||
|
@ -29,7 +29,7 @@ import moe.fuqiuluo.shamrock.tools.jsonArray
|
||||
import moe.fuqiuluo.shamrock.tools.respond
|
||||
|
||||
fun Routing.messageAction() {
|
||||
route("/send_group_forward_msg") {
|
||||
route("/send_group_forward_(msg|message)".toRegex()) {
|
||||
post {
|
||||
val groupId = fetchPostOrNull("group_id")
|
||||
val messages = fetchPostJsonArray("messages")
|
||||
@ -40,7 +40,7 @@ fun Routing.messageAction() {
|
||||
}
|
||||
}
|
||||
|
||||
route("/send_private_forward_msg") {
|
||||
route("/send_private_forward_(msg|message)".toRegex()) {
|
||||
post {
|
||||
val userId = fetchPostOrNull("user_id")
|
||||
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") {
|
||||
val id = fetchOrThrow("id")
|
||||
call.respondText(GetForwardMsg(id), ContentType.Application.Json)
|
||||
@ -101,27 +113,34 @@ fun Routing.messageAction() {
|
||||
get {
|
||||
val msgType = fetchGetOrThrow("message_type")
|
||||
val message = fetchGetOrThrow("message")
|
||||
val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3
|
||||
val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||
val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType)
|
||||
|
||||
val userId = fetchGetOrNull("user_id")
|
||||
val groupId = fetchGetOrNull("group_id")
|
||||
|
||||
val recallDuration = fetchGetOrNull("recall_duration")?.toLongOrNull()
|
||||
|
||||
call.respondText(SendMessage(
|
||||
chatType = chatType,
|
||||
peerId = if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!,
|
||||
message = message,
|
||||
autoEscape = autoEscape,
|
||||
fromId = groupId ?: userId ?: ""
|
||||
fromId = groupId ?: userId ?: "",
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
), ContentType.Application.Json)
|
||||
}
|
||||
post {
|
||||
val msgType = fetchPostOrThrow("message_type")
|
||||
val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType)
|
||||
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3
|
||||
|
||||
val userId = fetchPostOrNull("user_id")
|
||||
val groupId = fetchPostOrNull("group_id")
|
||||
val peerId = if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!
|
||||
val recallDuration = fetchPostOrNull("recall_duration")?.toLongOrNull()
|
||||
|
||||
call.respondText(if (isJsonData() && !isJsonString("message")) {
|
||||
if (isJsonObject("message")) {
|
||||
@ -129,14 +148,18 @@ fun Routing.messageAction() {
|
||||
chatType = chatType,
|
||||
peerId = peerId,
|
||||
message = listOf(fetchPostJsonObject("message")).jsonArray,
|
||||
fromId = groupId ?: userId ?: ""
|
||||
fromId = groupId ?: userId ?: "",
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
} else {
|
||||
SendMessage(
|
||||
chatType = chatType,
|
||||
peerId = peerId,
|
||||
message = fetchPostJsonArray("message"),
|
||||
fromId = groupId ?: userId ?: ""
|
||||
fromId = groupId ?: userId ?: "",
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@ -147,7 +170,9 @@ fun Routing.messageAction() {
|
||||
peerId = peerId,
|
||||
message = fetchPostOrThrow("message"),
|
||||
autoEscape = autoEscape,
|
||||
fromId = groupId ?: userId ?: ""
|
||||
fromId = groupId ?: userId ?: "",
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
}, ContentType.Application.Json)
|
||||
}
|
||||
@ -157,34 +182,43 @@ fun Routing.messageAction() {
|
||||
get {
|
||||
val groupId = fetchGetOrThrow("group_id")
|
||||
val message = fetchGetOrThrow("message")
|
||||
val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3
|
||||
val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||
call.respondText(SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, autoEscape))
|
||||
val recallDuration = fetchGetOrNull("recall_duration")?.toLongOrNull()
|
||||
|
||||
call.respondText(SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, message, autoEscape, retryCnt = retryCnt, recallDuration = recallDuration), ContentType.Application.Json)
|
||||
}
|
||||
post {
|
||||
val groupId = fetchPostOrThrow("group_id")
|
||||
|
||||
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3
|
||||
val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||
val recallDuration = fetchPostOrNull("recall_duration")?.toLongOrNull()
|
||||
|
||||
val result = if (isJsonData()) {
|
||||
if (isJsonString("message")) {
|
||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, fetchPostJsonString("message"), autoEscape)
|
||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, fetchPostJsonString("message"), autoEscape, retryCnt = retryCnt, recallDuration = recallDuration)
|
||||
} else {
|
||||
if (isJsonObject("message")) {
|
||||
SendMessage(
|
||||
chatType = MsgConstant.KCHATTYPEGROUP,
|
||||
peerId = groupId,
|
||||
message = listOf(fetchPostJsonObject("message")).jsonArray
|
||||
message = listOf(fetchPostJsonObject("message")).jsonArray,
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
} else {
|
||||
SendMessage(
|
||||
chatType = MsgConstant.KCHATTYPEGROUP,
|
||||
peerId = groupId,
|
||||
message = fetchPostJsonArray("message")
|
||||
message = fetchPostJsonArray("message"),
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, fetchPostOrThrow("message"), autoEscape)
|
||||
SendMessage(MsgConstant.KCHATTYPEGROUP, groupId, fetchPostOrThrow("message"), autoEscape, retryCnt = retryCnt, recallDuration = recallDuration)
|
||||
}
|
||||
|
||||
call.respondText(result, ContentType.Application.Json)
|
||||
@ -196,23 +230,27 @@ fun Routing.messageAction() {
|
||||
val userId = fetchGetOrThrow("user_id")
|
||||
val groupId = fetchGetOrNull("group_id")
|
||||
val message = fetchGetOrThrow("message")
|
||||
val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3
|
||||
val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||
val recallDuration = fetchGetOrNull("recall_duration")?.toLongOrNull()
|
||||
call.respondText(SendMessage(
|
||||
chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP,
|
||||
peerId = userId,
|
||||
message = message,
|
||||
autoEscape = autoEscape,
|
||||
fromId = groupId ?: userId
|
||||
fromId = groupId ?: userId,
|
||||
retryCnt = retryCnt, recallDuration = recallDuration
|
||||
), ContentType.Application.Json)
|
||||
}
|
||||
post {
|
||||
val userId = fetchPostOrThrow("user_id")
|
||||
val groupId = fetchPostOrNull("group_id")
|
||||
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3
|
||||
val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||
|
||||
val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
val fromId = groupId ?: userId
|
||||
|
||||
val recallDuration = fetchPostOrNull("recall_duration")?.toLongOrNull()
|
||||
|
||||
val result = if (isJsonData()) {
|
||||
if (isJsonString("message")) {
|
||||
@ -221,7 +259,8 @@ fun Routing.messageAction() {
|
||||
peerId = userId,
|
||||
message = fetchPostJsonString("message"),
|
||||
autoEscape = autoEscape,
|
||||
fromId = fromId
|
||||
fromId = fromId,
|
||||
retryCnt = retryCnt, recallDuration = recallDuration
|
||||
)
|
||||
} else {
|
||||
if (isJsonObject("message")) {
|
||||
@ -229,14 +268,16 @@ fun Routing.messageAction() {
|
||||
chatType = chatType,
|
||||
peerId = userId,
|
||||
message = listOf(fetchPostJsonObject("message")).jsonArray,
|
||||
fromId = fromId
|
||||
fromId = fromId,
|
||||
retryCnt = retryCnt, recallDuration = recallDuration
|
||||
)
|
||||
} else {
|
||||
SendMessage(
|
||||
chatType = chatType,
|
||||
peerId = userId,
|
||||
message = fetchPostJsonArray("message"),
|
||||
fromId = fromId
|
||||
fromId = fromId,
|
||||
retryCnt = retryCnt, recallDuration = recallDuration
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -246,7 +287,8 @@ fun Routing.messageAction() {
|
||||
peerId = userId,
|
||||
message = fetchPostOrThrow("message"),
|
||||
autoEscape = autoEscape,
|
||||
fromId = fromId
|
||||
fromId = fromId,
|
||||
retryCnt = retryCnt, recallDuration = recallDuration
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@ import moe.fuqiuluo.shamrock.helper.ErrorTokenException
|
||||
import io.ktor.server.application.createApplicationPlugin
|
||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.tools.fetchOrNull
|
||||
import java.net.URLDecoder
|
||||
import java.nio.charset.Charset
|
||||
|
||||
private suspend fun ApplicationCall.checkToken() {
|
||||
val token = ShamrockConfig.getToken()
|
||||
@ -12,10 +14,17 @@ private suspend fun ApplicationCall.checkToken() {
|
||||
return
|
||||
}
|
||||
var accessToken = request.headers["Authorization"]
|
||||
?: fetchOrNull("ticket")
|
||||
?: fetchOrNull("access_token")
|
||||
?: fetchOrNull("ticket")?.let {
|
||||
URLDecoder.decode(it)
|
||||
}
|
||||
?: fetchOrNull("access_token")?.let {
|
||||
URLDecoder.decode(it)
|
||||
}
|
||||
?: fetchOrNull("token")?.let {
|
||||
URLDecoder.decode(it)
|
||||
}
|
||||
?: throw ErrorTokenException
|
||||
if (accessToken.startsWith("Bearer ")) {
|
||||
if (accessToken.startsWith("Bearer ", ignoreCase = true)) {
|
||||
accessToken = accessToken.substring(7)
|
||||
}
|
||||
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.MsgSvc
|
||||
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.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
@ -14,6 +17,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.*
|
||||
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.helper.Level
|
||||
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
|
||||
|
||||
internal object HttpService: HttpTransmitServlet() {
|
||||
private val actionMsgTypes = arrayOf(
|
||||
"record", "voice", "video", "markdown"
|
||||
)
|
||||
|
||||
private val jobList = arrayListOf<Job>()
|
||||
|
||||
override fun submitFlowJob(job: Job) {
|
||||
@ -66,99 +72,83 @@ internal object HttpService: HttpTransmitServlet() {
|
||||
|
||||
private suspend fun handleQuicklyReply(record: MsgRecord, msgHash: Int, jsonText: String) {
|
||||
try {
|
||||
val data = Json.parseToJsonElement(jsonText).asJsonObject
|
||||
if (data.containsKey("reply")) {
|
||||
LogCenter.log({ "quickly reply successfully" }, Level.DEBUG)
|
||||
val autoEscape = data["auto_escape"].asBooleanOrNull ?: false
|
||||
val atSender = data["at_sender"].asBooleanOrNull ?: false
|
||||
val autoReply = data["auto_reply"].asBooleanOrNull ?: true
|
||||
val message = data["reply"]
|
||||
if (message is JsonPrimitive) {
|
||||
if (autoEscape) {
|
||||
val msgList = mutableSetOf<JsonElement>()
|
||||
msgList.add(mapOf(
|
||||
"type" to "text",
|
||||
"data" to mapOf(
|
||||
"text" to message.asString
|
||||
val data = Json.parseToJsonElement(jsonText)
|
||||
|
||||
if (data is JsonObject) {
|
||||
if (data.containsKey("reply")) {
|
||||
LogCenter.log({ "quickly reply successfully" }, Level.DEBUG)
|
||||
val autoEscape = data["auto_escape"].asBooleanOrNull ?: false
|
||||
val atSender = data["at_sender"].asBooleanOrNull ?: false
|
||||
val autoReply = data["auto_reply"].asBooleanOrNull ?: true
|
||||
val message = data["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
|
||||
)
|
||||
).json)
|
||||
} else {
|
||||
val messageArray = MessageHelper.decodeCQCode(message.asString)
|
||||
quicklyReply(
|
||||
record,
|
||||
messageArray,
|
||||
msgHash,
|
||||
atSender,
|
||||
autoReply
|
||||
)
|
||||
}
|
||||
} else if (message is JsonArray) {
|
||||
quicklyReply(
|
||||
record,
|
||||
msgList.jsonArray,
|
||||
msgHash,
|
||||
atSender,
|
||||
autoReply
|
||||
)
|
||||
} else {
|
||||
val messageArray = MessageHelper.decodeCQCode(message.asString)
|
||||
quicklyReply(
|
||||
record,
|
||||
messageArray,
|
||||
message,
|
||||
msgHash,
|
||||
atSender,
|
||||
autoReply
|
||||
)
|
||||
}
|
||||
} else if (message is JsonArray) {
|
||||
quicklyReply(
|
||||
record,
|
||||
message,
|
||||
msgHash,
|
||||
atSender,
|
||||
autoReply
|
||||
)
|
||||
}
|
||||
}
|
||||
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("delete") && data["delete"].asBoolean) {
|
||||
MsgSvc.recallMsg(msgHash)
|
||||
}
|
||||
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("kick") && data["kick"].asBoolean) {
|
||||
GroupSvc.kickMember(record.peerUin, false, record.senderUin)
|
||||
}
|
||||
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("ban") && data["ban"].asBoolean) {
|
||||
val banTime = data["ban_duration"].asIntOrNull ?: (30 * 60)
|
||||
if (banTime <= 0) return
|
||||
GroupSvc.banMember(record.peerUin, record.senderUin, banTime)
|
||||
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("delete") && data["delete"].asBoolean) {
|
||||
MsgSvc.recallMsg(msgHash)
|
||||
}
|
||||
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("kick") && data["kick"].asBoolean) {
|
||||
GroupSvc.kickMember(record.peerUin, false, record.senderUin)
|
||||
}
|
||||
if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("ban") && data["ban"].asBoolean) {
|
||||
val banTime = data["ban_duration"].asIntOrNull ?: (30 * 60)
|
||||
if (banTime <= 0) return
|
||||
GroupSvc.banMember(record.peerUin, record.senderUin, banTime)
|
||||
}
|
||||
} else if (data is JsonArray) {
|
||||
data.forEach {
|
||||
handleQuicklyActions(it.asJsonObject)
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
LogCenter.log("处理快速操作错误: $e", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
private 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
|
||||
}
|
||||
}
|
||||
private suspend fun handleQuicklyActions(data: JsonObject) {
|
||||
val action = data["action"].asString
|
||||
val echo = data["echo"] ?: EmptyJsonString
|
||||
|
||||
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) // 添加@发送者
|
||||
val params = data["params"].asJsonObjectOrNull ?: EmptyJsonObject
|
||||
|
||||
val handler = ActionManager[action]
|
||||
if (handler == null) {
|
||||
LogCenter.log("HTTP快速操作:不支持的Action: $action", Level.WARN)
|
||||
} else {
|
||||
handler.handle(ActionSession(params, echo))
|
||||
}
|
||||
messageList.addAll(message)
|
||||
MsgSvc.sendToAio(record.chatType, record.peerUin.toString(), JsonArray(messageList))
|
||||
}
|
||||
}
|
@ -18,6 +18,11 @@ internal class WebSocketClientService(
|
||||
) : WebSocketClientServlet(address, heartbeatInterval, wsHeaders) {
|
||||
private val eventJobList = mutableSetOf<Job>()
|
||||
|
||||
init {
|
||||
startHeartbeatTimer()
|
||||
initTransmitter()
|
||||
}
|
||||
|
||||
override fun submitFlowJob(job: Job) {
|
||||
eventJobList.add(job)
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ internal class WebSocketService(
|
||||
.ifNullOrEmpty(handshake.getFieldValue("ticket"))
|
||||
.ifNullOrEmpty(handshake.getFieldValue("Authorization"))
|
||||
?: throw ErrorTokenException
|
||||
if (accessToken.startsWith("Bearer ")) {
|
||||
if (accessToken.startsWith("Bearer ", ignoreCase = true)) {
|
||||
accessToken = accessToken.substring(7)
|
||||
}
|
||||
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.RequestType
|
||||
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.json
|
||||
import java.util.ArrayList
|
||||
@ -58,7 +59,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
||||
elements: ArrayList<MsgElement>,
|
||||
rawMsg: String,
|
||||
msgHash: Int,
|
||||
postType: PostType = PostType.Msg
|
||||
postType: PostType
|
||||
): Boolean {
|
||||
val uin = app.longAccountUin
|
||||
transMessageEvent(record,
|
||||
@ -81,10 +82,10 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
||||
sender = Sender(
|
||||
userId = record.senderUin,
|
||||
nickname = record.sendNickName
|
||||
.ifBlank { record.sendRemarkName }
|
||||
.ifBlank { record.sendMemberName }
|
||||
.ifBlank { record.peerName },
|
||||
card = record.sendMemberName.ifBlank { record.sendNickName },
|
||||
.ifEmpty { record.sendRemarkName }
|
||||
.ifEmpty { record.sendMemberName }
|
||||
.ifEmpty { record.peerName },
|
||||
card = record.sendMemberName,
|
||||
role = when (record.senderUin) {
|
||||
GroupSvc.getOwner(record.peerUin.toString()) -> MemberRole.Owner
|
||||
in GroupSvc.getAdminList(record.peerUin.toString()) -> MemberRole.Admin
|
||||
@ -106,12 +107,12 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
||||
elements: ArrayList<MsgElement>,
|
||||
rawMsg: String,
|
||||
msgHash: Int,
|
||||
postType: PostType = PostType.Msg,
|
||||
postType: PostType,
|
||||
tempSource: MessageTempSource = MessageTempSource.Unknown
|
||||
): Boolean {
|
||||
val botUin = app.longAccountUin
|
||||
var nickName = record.sendNickName
|
||||
if (nickName.isNullOrBlank()) {
|
||||
if (nickName.isNullOrEmpty()) {
|
||||
CardSvc.getProfileCard(record.senderUin.toString()).onSuccess {
|
||||
nickName = it.strNick ?: record.peerName
|
||||
}
|
||||
@ -222,6 +223,24 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
||||
* 群聊通知 通知器
|
||||
*/
|
||||
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 {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
@ -245,8 +264,10 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
||||
suspend fun transGroupMemberNumChanged(
|
||||
time: Long,
|
||||
target: Long,
|
||||
targetUid: String,
|
||||
groupCode: Long,
|
||||
operation: Long,
|
||||
operator: Long,
|
||||
operatorUid: String,
|
||||
noticeType: NoticeType,
|
||||
noticeSubType: NoticeSubType
|
||||
): Boolean {
|
||||
@ -256,11 +277,14 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
||||
postType = PostType.Notice,
|
||||
type = noticeType,
|
||||
subType = noticeSubType,
|
||||
operatorId = operation,
|
||||
operatorId = operator,
|
||||
userId = target,
|
||||
senderId = operation,
|
||||
senderId = operator,
|
||||
target = target,
|
||||
groupId = groupCode
|
||||
groupId = groupCode,
|
||||
targetUid = targetUid,
|
||||
operatorUid = operatorUid,
|
||||
userUid = targetUid
|
||||
))
|
||||
return true
|
||||
}
|
||||
@ -268,6 +292,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
||||
suspend fun transGroupAdminChanged(
|
||||
msgTime: Long,
|
||||
target: Long,
|
||||
targetUid: String,
|
||||
groupCode: Long,
|
||||
setAdmin: Boolean
|
||||
): Boolean {
|
||||
@ -279,6 +304,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
||||
subType = if (setAdmin) NoticeSubType.Set else NoticeSubType.UnSet,
|
||||
operatorId = 0,
|
||||
target = target,
|
||||
targetUid = targetUid,
|
||||
groupId = groupCode
|
||||
))
|
||||
return true
|
||||
@ -286,8 +312,11 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
||||
|
||||
suspend fun transGroupBan(
|
||||
msgTime: Long,
|
||||
operation: Long,
|
||||
subType: NoticeSubType,
|
||||
operator: Long,
|
||||
operatorUid: String,
|
||||
target: Long,
|
||||
targetUid: String,
|
||||
groupCode: Long,
|
||||
duration: Int
|
||||
): Boolean {
|
||||
@ -296,13 +325,15 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupBan,
|
||||
subType = if (duration == 0) NoticeSubType.LiftBan else NoticeSubType.Ban,
|
||||
operatorId = operation,
|
||||
subType = subType,
|
||||
operatorId = operator,
|
||||
userId = target,
|
||||
senderId = operation,
|
||||
senderId = operator,
|
||||
target = target,
|
||||
groupId = groupCode,
|
||||
duration = duration
|
||||
duration = duration,
|
||||
operatorUid = operatorUid,
|
||||
targetUid = targetUid
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ internal abstract class HttpTransmitServlet : BaseTransmitServlet {
|
||||
if (!allowTransmit()) return null
|
||||
try {
|
||||
if (address.startsWith("http://") || address.startsWith("https://")) {
|
||||
return GlobalClient.post(address) {
|
||||
val response = GlobalClient.post(address) {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(body)
|
||||
|
||||
@ -44,6 +44,11 @@ internal abstract class HttpTransmitServlet : BaseTransmitServlet {
|
||||
header("X-Client-Role", "Universal")
|
||||
header("Sec-WebSocket-Protocol", "11.Shamrock")
|
||||
}
|
||||
return if (response.status.value == 204) {
|
||||
null
|
||||
} else {
|
||||
response
|
||||
}
|
||||
} else {
|
||||
LogCenter.log("HTTP推送地址错误: ${address}。", Level.ERROR)
|
||||
}
|
||||
|
@ -35,24 +35,22 @@ import java.net.URI
|
||||
import kotlin.concurrent.timer
|
||||
|
||||
internal abstract class WebSocketClientServlet(
|
||||
url: String,
|
||||
private val url: String,
|
||||
private val heartbeatInterval: Long,
|
||||
wsHeaders: Map<String, String>
|
||||
private val wsHeaders: Map<String, String>
|
||||
) : BaseTransmitServlet, WebSocketClient(URI(url), wsHeaders) {
|
||||
init {
|
||||
if (connectedClients.containsKey(url)) {
|
||||
throw RuntimeException("WebSocketClient已存在: $url")
|
||||
}
|
||||
}
|
||||
|
||||
private val sendLock = Mutex()
|
||||
|
||||
override fun allowTransmit(): Boolean {
|
||||
return ShamrockConfig.openWebSocketClient()
|
||||
}
|
||||
|
||||
override fun onOpen(handshakedata: ServerHandshake?) {
|
||||
LogCenter.log("WebSocketClient onOpen: ${handshakedata?.httpStatus}, ${handshakedata?.httpStatusMessage}")
|
||||
|
||||
startHeartbeatTimer()
|
||||
pushMetaLifecycle()
|
||||
initTransmitter()
|
||||
}
|
||||
|
||||
override fun onMessage(message: String) {
|
||||
GlobalScope.launch {
|
||||
handleMessage(message)
|
||||
@ -84,14 +82,34 @@ internal abstract class WebSocketClientServlet(
|
||||
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) {
|
||||
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")
|
||||
cancelFlowJobs()
|
||||
connectedClients.remove(url)
|
||||
}
|
||||
|
||||
override fun onError(ex: Exception?) {
|
||||
LogCenter.log("WebSocketClient onError: ${ex?.message}")
|
||||
cancelFlowJobs()
|
||||
connectedClients.remove(url)
|
||||
}
|
||||
|
||||
protected suspend inline fun <reified T> pushTo(body: T) {
|
||||
@ -105,11 +123,14 @@ internal abstract class WebSocketClientServlet(
|
||||
}
|
||||
}
|
||||
|
||||
private fun startHeartbeatTimer() {
|
||||
if (heartbeatInterval <= 0) return
|
||||
fun startHeartbeatTimer() {
|
||||
if (heartbeatInterval <= 0) {
|
||||
LogCenter.log("被动WebSocket心跳间隔为0,不启动心跳", Level.WARN)
|
||||
return
|
||||
}
|
||||
timer(
|
||||
name = "heartbeat",
|
||||
initialDelay = 0,
|
||||
initialDelay = heartbeatInterval,
|
||||
period = heartbeatInterval,
|
||||
) {
|
||||
if (isClosed || isClosing || !isOpen) {
|
||||
@ -117,6 +138,7 @@ internal abstract class WebSocketClientServlet(
|
||||
return@timer
|
||||
}
|
||||
val runtime = AppRuntimeFetcher.appRuntime
|
||||
LogCenter.log("WebSocketClient心跳: ${app.longAccountUin}", Level.DEBUG)
|
||||
send(
|
||||
GlobalJson.encodeToString(
|
||||
PushMetaEvent(
|
||||
@ -131,7 +153,7 @@ internal abstract class WebSocketClientServlet(
|
||||
status = "正常",
|
||||
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) {
|
||||
val runtime = AppRuntimeFetcher.appRuntime
|
||||
val curUin = runtime.currentAccountUin
|
||||
LogCenter.log("WebSocket心跳: $curUin", Level.DEBUG)
|
||||
broadcastAnyEvent(
|
||||
PushMetaEvent(
|
||||
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") {
|
||||
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) {
|
||||
|
@ -76,6 +76,7 @@ internal object ShamrockConfig {
|
||||
|
||||
putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息
|
||||
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)
|
||||
}
|
||||
@ -101,6 +102,10 @@ internal object ShamrockConfig {
|
||||
return Config.rules?.privateRule
|
||||
}
|
||||
|
||||
fun enableSyncMsgAsSentMsg(): Boolean {
|
||||
return mmkv.getBoolean("enable_sync_msg_as_sent_msg", false)
|
||||
}
|
||||
|
||||
fun enableSelfMsg(): Boolean {
|
||||
return mmkv.getBoolean("enable_self_msg", false)
|
||||
}
|
||||
|
@ -16,3 +16,22 @@ internal data class FriendEntry(
|
||||
@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("user_name") val name: String,
|
||||
@SerialName("sex") val sex: String,
|
||||
@SerialName("age") val age: Int,
|
||||
@SerialName("title") val title: String,
|
||||
@SerialName("title_expire_time") val titleExpireTime: Int,
|
||||
@SerialName("nickname") val nick: String,
|
||||
@ -42,6 +43,7 @@ internal data class SimpleTroopMemberInfo(
|
||||
@SerialName("role") val role: MemberRole,
|
||||
@SerialName("unfriendly") val unfriendly: Boolean,
|
||||
@SerialName("card_changeable") val cardChangeable: Boolean,
|
||||
@SerialName("shut_up_timestamp") val shutUpTimestamp: Long?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
@ -20,7 +20,7 @@ internal enum class NoticeType {
|
||||
|
||||
@Serializable
|
||||
internal enum class RequestType {
|
||||
@SerialName("friend ") Friend,
|
||||
@SerialName("friend") Friend,
|
||||
@SerialName("group") Group,
|
||||
}
|
||||
|
||||
@ -42,6 +42,8 @@ internal enum class NoticeSubType {
|
||||
@SerialName("kick_me") KickMe,
|
||||
|
||||
@SerialName("poke") Poke,
|
||||
@SerialName("sign") Sign,
|
||||
|
||||
|
||||
@SerialName("title") Title,
|
||||
@SerialName("delete") Delete,
|
||||
@ -65,14 +67,20 @@ internal data class NoticeEvent(
|
||||
@SerialName("post_type") val postType: PostType,
|
||||
@SerialName("notice_type") val type: NoticeType,
|
||||
@SerialName("sub_type") val subType: NoticeSubType = NoticeSubType.None,
|
||||
@SerialName("group_id") val groupId: Long = 0,
|
||||
@SerialName("operator_id") val operatorId: Long = 0,
|
||||
@SerialName("user_id") val userId: Long = 0,
|
||||
@SerialName("sender_id") val senderId: Long = 0,
|
||||
@SerialName("duration") val duration: Int = 0,
|
||||
@SerialName("message_id") val msgId: Int = 0,
|
||||
@SerialName("group_id") val groupId: Long = Long.MIN_VALUE,
|
||||
@SerialName("operator_id") val operatorId: Long = Long.MIN_VALUE,
|
||||
@SerialName("operator_uid") val operatorUid: String = "",
|
||||
@SerialName("user_id") val userId: Long = Long.MIN_VALUE,
|
||||
@SerialName("user_uid") val userUid: String = "",
|
||||
@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("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("private_file") val privateFile: PrivateFileMsg? = null,
|
||||
@SerialName("flag") val flag: String? = null,
|
||||
@ -87,7 +95,10 @@ internal data class NoticeEvent(
|
||||
// 戳一戳
|
||||
@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("request_type") val type: RequestType,
|
||||
@SerialName("sub_type") val subType: RequestSubType = RequestSubType.None,
|
||||
@SerialName("group_id") val groupId: Long = 0,
|
||||
@SerialName("user_id") val userId: Long = 0,
|
||||
@SerialName("group_id") val groupId: Long = -1,
|
||||
@SerialName("user_id") val userId: Long = -1,
|
||||
@SerialName("comment") val comment: String = "",
|
||||
@SerialName("flag") val flag: String? = null,
|
||||
)
|
||||
@ -131,4 +142,11 @@ internal data class PokeDetail (
|
||||
val suffix: String? = "",
|
||||
@SerialName("action_img_url")
|
||||
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)
|
||||
|
||||
package moe.fuqiuluo.shamrock.remote.service.listener
|
||||
|
||||
import android.os.Build
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||
import com.tencent.qqnt.kernel.nativeinterface.*
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toCQCode
|
||||
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.data.push.MessageTempSource
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.push.PostType
|
||||
import mqq.app.MobileQQ
|
||||
import java.util.ArrayList
|
||||
import java.util.Collections
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
internal object AioListener: IKernelMsgListener {
|
||||
internal object AioListener : IKernelMsgListener {
|
||||
// 通过MSG SEQ临时监听器
|
||||
internal val messageLessListenerMap = Collections.synchronizedMap(HashMap<Long, MsgRecord.() -> Unit>())
|
||||
|
||||
@ -43,13 +41,12 @@ internal object AioListener: IKernelMsgListener {
|
||||
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return // TODO: 频道消息暂不处理
|
||||
|
||||
messageLessListenerMap.firstNotNullOfOrNull {
|
||||
if(it.key == record.msgSeq) it else null
|
||||
if (it.key == record.msgSeq) it else null
|
||||
}?.let {
|
||||
it.value(record)
|
||||
messageLessListenerMap.remove(it.key)
|
||||
}
|
||||
|
||||
if (record.msgSeq < 0) return
|
||||
if (record.msgSeq < 0) return
|
||||
|
||||
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
|
||||
|
||||
@ -67,9 +64,14 @@ internal object AioListener: IKernelMsgListener {
|
||||
if (rawMsg.isEmpty()) return
|
||||
|
||||
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")) {
|
||||
// LogCenter.log(record.extInfoForUI.decodeToString(), Level.WARN)
|
||||
//}
|
||||
@ -83,7 +85,7 @@ internal object AioListener: IKernelMsgListener {
|
||||
}
|
||||
|
||||
if(!GlobalEventTransmitter.MessageTransmitter.transGroupMessage(
|
||||
record, record.elements, rawMsg, msgHash
|
||||
record, record.elements, rawMsg, msgHash, postType
|
||||
)) {
|
||||
LogCenter.log("群消息推送失败 -> 推送目标可能不存在", Level.WARN)
|
||||
}
|
||||
@ -96,7 +98,7 @@ internal object AioListener: IKernelMsgListener {
|
||||
}
|
||||
|
||||
if(!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage(
|
||||
record, record.elements, rawMsg, msgHash
|
||||
record, record.elements, rawMsg, msgHash, postType
|
||||
)) {
|
||||
LogCenter.log("私聊消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||
}
|
||||
@ -112,8 +114,8 @@ internal object AioListener: IKernelMsgListener {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -158,7 +160,8 @@ internal object AioListener: IKernelMsgListener {
|
||||
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return@forEach// TODO: 频道消息暂不处理
|
||||
|
||||
if (record.sendStatus == MsgConstant.KSENDSTATUSFAILED
|
||||
|| record.sendStatus == MsgConstant.KSENDSTATUSSENDING) {
|
||||
|| record.sendStatus == MsgConstant.KSENDSTATUSSENDING
|
||||
) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
@ -182,8 +185,10 @@ internal object AioListener: IKernelMsgListener {
|
||||
.updateMsgSeqByMsgHash(msgHash, record.msgSeq.toInt())
|
||||
}
|
||||
|
||||
if (!ShamrockConfig.enableSelfMsg() || record.senderUin != TicketSvc.getLongUin())
|
||||
return@launch
|
||||
if (!ShamrockConfig.enableSelfMsg()
|
||||
|| record.senderUin != TicketSvc.getLongUin()
|
||||
|| record.peerUin == TicketSvc.getLongUin()
|
||||
) return@launch
|
||||
|
||||
val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString())
|
||||
if (rawMsg.isEmpty()) return@launch
|
||||
@ -191,24 +196,37 @@ internal object AioListener: IKernelMsgListener {
|
||||
|
||||
when (record.chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> {
|
||||
if(!GlobalEventTransmitter.MessageTransmitter
|
||||
.transGroupMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent)) {
|
||||
if (!GlobalEventTransmitter.MessageTransmitter
|
||||
.transGroupMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent)
|
||||
) {
|
||||
LogCenter.log("自发群消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
MsgConstant.KCHATTYPEC2C -> {
|
||||
if(!GlobalEventTransmitter.MessageTransmitter
|
||||
.transPrivateMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent)) {
|
||||
if (!GlobalEventTransmitter.MessageTransmitter
|
||||
.transPrivateMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent)
|
||||
) {
|
||||
LogCenter.log("自发私聊消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||
if (!ShamrockConfig.allowTempSession()) return@launch
|
||||
if(!GlobalEventTransmitter.MessageTransmitter
|
||||
.transPrivateMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent, MessageTempSource.Group)) {
|
||||
if (!GlobalEventTransmitter.MessageTransmitter
|
||||
.transPrivateMessage(
|
||||
record,
|
||||
record.elements,
|
||||
rawMsg,
|
||||
msgHash,
|
||||
PostType.MsgSent,
|
||||
MessageTempSource.Group
|
||||
)
|
||||
) {
|
||||
LogCenter.log("自发私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
else -> LogCenter.log("不支持SELF PUSH事件: ${record.chatType}")
|
||||
}
|
||||
}
|
||||
@ -305,7 +323,7 @@ internal object AioListener: IKernelMsgListener {
|
||||
override fun onFileMsgCome(arrayList: ArrayList<MsgRecord>?) {
|
||||
arrayList?.forEach { record ->
|
||||
GlobalScope.launch {
|
||||
when(record.chatType) {
|
||||
when (record.chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> onGroupFileMsg(record)
|
||||
MsgConstant.KCHATTYPEC2C -> onC2CFileMsg(record)
|
||||
else -> LogCenter.log("不支持该来源的文件上传事件:${record}", Level.WARN)
|
||||
@ -330,8 +348,9 @@ internal object AioListener: IKernelMsgListener {
|
||||
val fileSubId = fileMsg.fileSubId ?: ""
|
||||
val url = RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
|
||||
|
||||
if(!GlobalEventTransmitter.FileNoticeTransmitter
|
||||
.transPrivateFileEvent(record.msgTime, userId, fileId, fileSubId, fileName, fileSize, expireTime, url)) {
|
||||
if (!GlobalEventTransmitter.FileNoticeTransmitter
|
||||
.transPrivateFileEvent(record.msgTime, userId, fileId, fileSubId, fileName, fileSize, expireTime, url)
|
||||
) {
|
||||
LogCenter.log("私聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
|
||||
}
|
||||
}
|
||||
@ -353,8 +372,9 @@ internal object AioListener: IKernelMsgListener {
|
||||
|
||||
val url = RichProtoSvc.getGroupFileDownUrl(record.peerUin, uuid, bizId)
|
||||
|
||||
if(!GlobalEventTransmitter.FileNoticeTransmitter
|
||||
.transGroupFileEvent(record.msgTime, userId, groupId, uuid, fileName, fileSize, bizId, url)) {
|
||||
if (!GlobalEventTransmitter.FileNoticeTransmitter
|
||||
.transGroupFileEvent(record.msgTime, userId, groupId, uuid, fileName, fileSize, bizId, url)
|
||||
) {
|
||||
LogCenter.log("群聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
@ -12,17 +12,11 @@ import kotlinx.io.core.discardExact
|
||||
import kotlinx.io.core.readBytes
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import moe.fuqiuluo.proto.ProtoByteString
|
||||
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.proto.*
|
||||
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.TicketSvc.getLongUin
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeSubType
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeType
|
||||
@ -54,7 +48,6 @@ internal object PrimitiveListener {
|
||||
if (
|
||||
!pb.has(1, 3)
|
||||
|| !pb.has(1, 2)
|
||||
// || !pb.has(1, 2, 2)
|
||||
|| !pb.has(1, 2, 6)
|
||||
) return
|
||||
val msgType = pb[1, 2, 1].asInt
|
||||
@ -63,28 +56,32 @@ internal object PrimitiveListener {
|
||||
subType = pb[1, 2, 2].asInt
|
||||
}
|
||||
val msgTime = pb[1, 2, 6].asLong
|
||||
when (msgType) {
|
||||
33 -> onGroupMemIncreased(msgTime, pb)
|
||||
34 -> onGroupMemberDecreased(msgTime, pb)
|
||||
44 -> onGroupAdminChange(msgTime, pb)
|
||||
84 -> onGroupApply(msgTime, pb)
|
||||
87 -> onInviteGroup(msgTime, pb)
|
||||
528 -> when (subType) {
|
||||
35 -> onFriendApply(msgTime, pb)
|
||||
39 -> onCardChange(msgTime, pb)
|
||||
// invite
|
||||
68 -> onGroupApply(msgTime, pb)
|
||||
138 -> onC2CRecall(msgTime, pb)
|
||||
290 -> onC2cPoke(msgTime, pb)
|
||||
}
|
||||
try {
|
||||
when (msgType) {
|
||||
33 -> onGroupMemIncreased(msgTime, pb)
|
||||
34 -> onGroupMemberDecreased(msgTime, pb)
|
||||
44 -> onGroupAdminChange(msgTime, pb)
|
||||
84 -> onGroupApply(msgTime, pb)
|
||||
87 -> onInviteGroup(msgTime, pb)
|
||||
528 -> when (subType) {
|
||||
35 -> onFriendApply(msgTime, pb)
|
||||
39 -> onCardChange(msgTime, pb)
|
||||
// invite
|
||||
68 -> onGroupApply(msgTime, pb)
|
||||
138 -> onC2CRecall(msgTime, pb)
|
||||
290 -> onC2cPoke(msgTime, pb)
|
||||
}
|
||||
|
||||
732 -> when (subType) {
|
||||
12 -> onGroupBan(msgTime, pb)
|
||||
16 -> onGroupTitleChange(msgTime, pb)
|
||||
17 -> onGroupRecall(msgTime, pb)
|
||||
20 -> onGroupPoke(msgTime, pb)
|
||||
21 -> onEssenceMessage(msgTime, pb)
|
||||
732 -> when (subType) {
|
||||
12 -> onGroupBan(msgTime, pb)
|
||||
16 -> onGroupTitleChange(msgTime, pb)
|
||||
17 -> onGroupRecall(msgTime, pb)
|
||||
20 -> onGroupPokeAndGroupSign(msgTime, pb)
|
||||
21 -> onEssenceMessage(msgTime, pb)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogCenter.log("onMsgPush(msgType: $msgType, subType: $subType): "+e.stackTraceToString(), Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,8 +150,21 @@ internal object PrimitiveListener {
|
||||
|
||||
|
||||
private suspend fun onCardChange(msgTime: Long, pb: ProtoMap) {
|
||||
val targetId = pb[1, 3, 2, 1, 13, 2].asUtf8String
|
||||
val newCardList = pb[1, 3, 2, 1, 13, 3].asList
|
||||
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("onCardChange error: ${e.stackTraceToString()}", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
val targetId = detail[1, 13, 2].asUtf8String
|
||||
val newCardList = detail[1, 13, 3].asList
|
||||
var newCard = ""
|
||||
newCardList
|
||||
.value
|
||||
@ -163,7 +173,7 @@ internal object PrimitiveListener {
|
||||
newCard = it[2].asUtf8String
|
||||
}
|
||||
}
|
||||
val groupId = pb[1, 3, 2, 1, 13, 4].asLong
|
||||
val groupId = detail[1, 13, 4].asLong
|
||||
var oldCard = ""
|
||||
val targetQQ = ContactHelper.getUinByUidAsync(targetId).toLong()
|
||||
LogCenter.log("群组[$groupId]成员$targetQQ 群名片变动 -> $newCard")
|
||||
@ -181,20 +191,35 @@ internal object PrimitiveListener {
|
||||
}
|
||||
|
||||
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)
|
||||
val detail = if (readPacket.readBuf32Long() == groupCode) {
|
||||
readPacket.discardExact(1)
|
||||
ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
||||
} else pb[1, 3, 2]
|
||||
detail = if (detail[5] is ProtoList) {
|
||||
(detail[5] as ProtoList).value[0]
|
||||
} else {
|
||||
detail[5]
|
||||
}
|
||||
|
||||
val targetUin = detail[5, 5].asLong
|
||||
|
||||
val groupId = detail[4].asLong
|
||||
val targetUin = detail[5].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\"}>头衔
|
||||
val titleChangeInfo = detail[5, 2].asUtf8String
|
||||
val titleChangeInfo = detail[2].asUtf8String
|
||||
if (titleChangeInfo.indexOf("群主授予") == -1) {
|
||||
return
|
||||
}
|
||||
@ -212,15 +237,25 @@ internal object PrimitiveListener {
|
||||
}
|
||||
|
||||
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)
|
||||
val detail = if (readPacket.readBuf32Long() == groupCode) {
|
||||
readPacket.discardExact(1)
|
||||
ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
||||
} else pb[1, 3, 2]
|
||||
|
||||
val groupId = detail[4].asLong
|
||||
var groupId:Long
|
||||
try {
|
||||
groupId = detail[4].asULong
|
||||
}catch (e: ClassCastException){
|
||||
groupId = detail[4].asList.value[0].asULong
|
||||
}
|
||||
val mesSeq = detail[37].asInt
|
||||
val senderUin = detail[33, 5].asLong
|
||||
val operatorUin = detail[33, 6].asLong
|
||||
@ -254,32 +289,38 @@ internal object PrimitiveListener {
|
||||
}
|
||||
|
||||
|
||||
private suspend fun onGroupPoke(time: Long, pb: ProtoMap) {
|
||||
val groupCode1 = pb[1, 1, 1].asULong
|
||||
|
||||
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]
|
||||
private suspend fun onGroupPokeAndGroupSign(time: Long, pb: ProtoMap) {
|
||||
var detail = pb[1, 3, 2]
|
||||
if (detail !is ProtoMap) {
|
||||
groupCode = groupCode2
|
||||
readPacket.discardExact(1)
|
||||
detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
||||
try {
|
||||
val readPacket = ByteReadPacket(detail.asByteArray)
|
||||
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 operation: String
|
||||
var action: String? = null
|
||||
var suffix: String? = null
|
||||
var actionImg: String? = null
|
||||
detail[26][7]
|
||||
var rankImg: String? = null
|
||||
detail[7]
|
||||
.asList
|
||||
.value
|
||||
.forEach {
|
||||
@ -287,18 +328,42 @@ internal object PrimitiveListener {
|
||||
when (it[1].asUtf8String) {
|
||||
"uin_str1" -> operation = value
|
||||
"uin_str2" -> target = value
|
||||
// "nick_str1" -> operation_nick = value
|
||||
// "nick_str2" -> operation_nick = value
|
||||
"action_str" -> action = value
|
||||
"alt_str1" -> action = value
|
||||
"suffix_str" -> suffix = 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
|
||||
.transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupCode)
|
||||
) {
|
||||
LogCenter.log("群戳一戳推送失败!", Level.WARN)
|
||||
1068 -> {
|
||||
LogCenter.log("群打卡($groupId): $action $target")
|
||||
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||
.transGroupSign(time, target.toLong(), action, rankImg, groupId)
|
||||
) {
|
||||
LogCenter.log("群打卡推送失败!", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
LogCenter.log("onGroupPokeAndGroupSign unknown type ${detail[2].asInt}", Level.WARN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,13 +391,21 @@ internal object PrimitiveListener {
|
||||
val groupCode = pb[1, 3, 2, 1].asULong
|
||||
val targetUid = pb[1, 3, 2, 3].asUtf8String
|
||||
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()
|
||||
LogCenter.log("群成员增加($groupCode): $target, type = $type")
|
||||
|
||||
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||
.transGroupMemberNumChanged(
|
||||
time, target, groupCode, operation, NoticeType.GroupMemIncrease, when (type) {
|
||||
time, target, targetUid, groupCode, operator, operatorUid, NoticeType.GroupMemIncrease, when (type) {
|
||||
130 -> NoticeSubType.Approve
|
||||
131 -> NoticeSubType.Invite
|
||||
else -> NoticeSubType.Approve
|
||||
@ -347,8 +420,19 @@ internal object PrimitiveListener {
|
||||
val groupCode = pb[1, 3, 2, 1].asULong
|
||||
val targetUid = pb[1, 3, 2, 3].asUtf8String
|
||||
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 subtype = when (type) {
|
||||
130 -> NoticeSubType.Leave
|
||||
@ -362,7 +446,7 @@ internal object PrimitiveListener {
|
||||
LogCenter.log("群成员减少($groupCode): $target, type = $subtype ($type)")
|
||||
|
||||
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)
|
||||
}
|
||||
@ -383,40 +467,55 @@ internal object PrimitiveListener {
|
||||
LogCenter.log("群管理员变动($groupCode): $target, isSetAdmin = $isSetAdmin")
|
||||
|
||||
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||
.transGroupAdminChanged(msgTime, target, groupCode, isSetAdmin)
|
||||
.transGroupAdminChanged(msgTime, target, targetUid, groupCode, isSetAdmin)
|
||||
) {
|
||||
LogCenter.log("群管理员变动推送失败!", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
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 targetUid = pb[1, 3, 2, 5, 3, 1].asUtf8String
|
||||
val duration = pb[1, 3, 2, 5, 3, 2].asInt
|
||||
val operation = ContactHelper.getUinByUidAsync(operatorUid).toLong()
|
||||
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
|
||||
LogCenter.log("群禁言($groupCode): $operation -> $target, 时长 = ${duration}s")
|
||||
val wholeBan = !pb.has(1, 3, 2, 5, 3, 1)
|
||||
val targetUid = if (wholeBan) "" else pb[1, 3, 2, 5, 3, 1].asUtf8String
|
||||
val rawDuration = pb[1, 3, 2, 5, 3, 2].asInt
|
||||
|
||||
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
|
||||
.transGroupBan(msgTime, operation, target, groupCode, duration)
|
||||
.transGroupBan(msgTime, subType, operator, operatorUid, target, targetUid, groupCode, duration)
|
||||
) {
|
||||
LogCenter.log("群禁言推送失败!", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onGroupRecall(time: Long, pb: ProtoMap) {
|
||||
val groupCode = pb[1, 1, 1].asULong
|
||||
val readPacket = ByteReadPacket(pb[1, 3, 2].asByteArray)
|
||||
try {
|
||||
/**
|
||||
* 真是不理解这个傻呗设计,有些群是正常的Protobuf,有些群要去掉7字节
|
||||
*/
|
||||
val detail = if (readPacket.readBuf32Long() == groupCode) {
|
||||
readPacket.discardExact(1)
|
||||
ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
||||
} else pb[1, 3, 2]
|
||||
|
||||
var detail = pb[1, 3, 2]
|
||||
if (detail !is ProtoMap) {
|
||||
try {
|
||||
val readPacket = ByteReadPacket(detail.asByteArray)
|
||||
readPacket.discardExact(4)
|
||||
readPacket.discardExact(1)
|
||||
detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt()))
|
||||
readPacket.release()
|
||||
} catch (e: Exception) {
|
||||
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 targetUid = detail[11, 3, 6].asUtf8String
|
||||
val msgSeq = detail[11, 3, 1].asLong
|
||||
@ -436,9 +535,6 @@ internal object PrimitiveListener {
|
||||
) {
|
||||
LogCenter.log("群消息撤回推送失败!", Level.WARN)
|
||||
}
|
||||
} finally {
|
||||
readPacket.release()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onGroupApply(time: Long, pb: ProtoMap) {
|
||||
@ -448,16 +544,18 @@ internal object PrimitiveListener {
|
||||
val applierUid = pb[1, 3, 2, 3].asUtf8String
|
||||
val reason = pb[1, 3, 2, 5].asUtf8String
|
||||
val applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
|
||||
|
||||
if (applier == getLongUin()) {
|
||||
return
|
||||
}
|
||||
LogCenter.log("入群申请($groupCode) $applier: \"$reason\"")
|
||||
val flag = try {
|
||||
var reqs = requestGroupSystemMsgNew(10, 1)
|
||||
val riskReqs = requestGroupSystemMsgNew(10, 2)
|
||||
val riskReqs = requestGroupSystemMsgNew(5, 2)
|
||||
reqs = reqs + riskReqs
|
||||
val req = reqs.first {
|
||||
val req = reqs.firstOrNull() {
|
||||
it.msg_time.get() == time
|
||||
}
|
||||
val seq = req.msg_seq?.get()
|
||||
val seq = req?.msg_seq?.get() ?: time
|
||||
"$seq;$groupCode;$applier"
|
||||
} catch (err: Throwable) {
|
||||
"$time;$groupCode;$applier"
|
||||
@ -473,6 +571,9 @@ internal object PrimitiveListener {
|
||||
val groupCode = pb[1, 3, 2, 2, 3].asULong
|
||||
val applierUid = pb[1, 3, 2, 2, 5].asUtf8String
|
||||
val applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
|
||||
if (applier == getLongUin()) {
|
||||
return
|
||||
}
|
||||
if (pb[1, 3, 2, 2, 1].asInt < 3) {
|
||||
// todo
|
||||
return
|
||||
@ -480,17 +581,17 @@ internal object PrimitiveListener {
|
||||
LogCenter.log("邀请入群申请($groupCode): $applier")
|
||||
val flag = try {
|
||||
var reqs = requestGroupSystemMsgNew(10, 1)
|
||||
val riskReqs = requestGroupSystemMsgNew(10, 2)
|
||||
val riskReqs = requestGroupSystemMsgNew(5, 2)
|
||||
reqs = reqs + riskReqs
|
||||
val req = reqs.first {
|
||||
val req = reqs.firstOrNull() {
|
||||
it.msg_time.get() == time
|
||||
}
|
||||
val seq = req.msg_seq?.get()
|
||||
val seq = req?.msg_seq?.get() ?: time
|
||||
"$seq;$groupCode;$applier"
|
||||
} catch (err: Throwable) {
|
||||
"$time;$groupCode;$applierUid"
|
||||
"$time;$groupCode;$applier"
|
||||
}
|
||||
if (GlobalEventTransmitter.RequestTransmitter
|
||||
if (!GlobalEventTransmitter.RequestTransmitter
|
||||
.transGroupApply(time, applier, "", groupCode, flag, RequestSubType.Add)
|
||||
) {
|
||||
LogCenter.log("邀请入群申请推送失败!", Level.WARN)
|
||||
@ -509,19 +610,19 @@ internal object PrimitiveListener {
|
||||
var reqs = requestGroupSystemMsgNew(10, 1)
|
||||
val riskReqs = requestGroupSystemMsgNew(10, 2)
|
||||
reqs = reqs + riskReqs
|
||||
val req = reqs.first {
|
||||
val req = reqs.firstOrNull() {
|
||||
it.msg_time.get() == time
|
||||
}
|
||||
val seq = req.msg_seq?.get()
|
||||
val seq = req?.msg_seq?.get() ?: time
|
||||
"$seq;$groupCode;$uin"
|
||||
} catch (err: Throwable) {
|
||||
"$time;$groupCode;$uin"
|
||||
}
|
||||
if (GlobalEventTransmitter.RequestTransmitter
|
||||
if (!GlobalEventTransmitter.RequestTransmitter
|
||||
.transGroupApply(time, invitor, "", groupCode, flag, RequestSubType.Invite)
|
||||
) {
|
||||
LogCenter.log("邀请入群推送失败!", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user