49 Commits

Author SHA1 Message Date
35c82fcc51 Shamrock: fix #169 2023-12-21 00:55:56 +08:00
89a4912ed7 Shamrock: fix #166 2023-12-20 18:58:17 +08:00
aeabc66067 Shamrock: 兼容性正反向HTTP调整 2023-12-20 18:52:24 +08:00
ccbfc9a1e1 Shamrock: fix #159 2023-12-19 22:11:22 +08:00
31936feb98 Merge remote-tracking branch 'origin/master' 2023-12-19 22:10:03 +08:00
538db69754 Shamrock: fix #159 2023-12-19 22:09:23 +08:00
25ea5bd6a8 Merge pull request #162 from callng/master
`Shamrock`: Upgrade build tools
2023-12-19 22:07:32 +08:00
460cd84258 Shamrock: Upgrade build tools 2023-12-19 21:13:28 +08:00
3f9613c43c Shamrock: 输出心跳日志 2023-12-19 21:07:28 +08:00
34eccda233 Shamrock: Määritetty tunnus ei vaadi suuraakkosia 2023-12-19 01:21:08 +08:00
741d2c7a84 Shamrock: fix #117 2023-12-19 01:13:39 +08:00
6ee5ceb321 Shamrock: fix #155 2023-12-18 21:15:56 +08:00
6107ec6ffb Shamrock: fix #156 2023-12-17 11:40:05 +08:00
4ef014a8ac Shamrock: fix card event and kickme operation 2023-12-13 15:09:46 +08:00
135a7c2f56 Shamrock: 优化构建流程 2023-12-13 01:23:40 +08:00
420f11784d Shamrock: fix field error 2023-12-13 00:22:06 +08:00
951e7462c4 Shamrock: add shut_up_timestamp field to get_group_member_list 2023-12-13 00:18:57 +08:00
1b0550b5e1 readme: fix broken link 2023-12-12 17:17:31 +08:00
919c4a7d80 Shamrock: fix #149 again 2023-12-12 11:11:21 +08:00
dcb2b0a26f Shamrock: fix #149 2023-12-12 10:28:24 +08:00
d388e5df0c Shamrock: fix #149 2023-12-11 14:29:07 +08:00
5776524579 Shamrock: 好友请求系统消息 2023-12-10 18:40:33 +08:00
1d0a0731fb Shamrock: 修复群消息撤回事件群号获取错误 2023-12-10 01:57:38 +08:00
4dafa75944 Shamrock: 修复群禁言事件群号获取错误 #148 2023-12-10 01:21:38 +08:00
85cdc86b07 Merge pull request #144 from PisLuanyao/master
`Clover.cpp`: NoxAppPlayer里的鸡脚so
2023-12-10 01:09:21 +08:00
cd19426d1b Clover.cpp: NoxAppPlayer
稍微动了一下,方便看,希望没搞错
2023-12-09 19:14:22 +08:00
48b1b40e1c Clover.cpp: NoxAppPlayer
也许是无效的?
2023-12-09 18:56:00 +08:00
50d469cc45 Clover.cpp: NoxAppPlayer
夜神模拟器里的鸡脚so
2023-12-09 18:41:53 +08:00
37f74d5284 Shamrock: 优化检测逻辑 2023-12-09 16:18:30 +08:00
adb7b12c16 Shamrock: feat 群打卡 2023-12-08 21:52:35 +08:00
27791cc848 Shamrock: fix friend request type typo 2023-12-08 12:54:34 +08:00
bd45523e25 Shamrock: 修正被动WebSocket日志 2023-12-07 23:44:55 +08:00
c0f2242679 Shamrock: 修复防止系统打盹错误 2023-12-07 00:24:06 +08:00
c309a2b3ed Shamrock: fix 部分事件日志错误打印 2023-12-06 16:42:00 +08:00
48c9048a00 Merge branch 'master' of github.com:whitechi73/OpenShamrock 2023-12-06 15:53:11 +08:00
4d2f7a794b Shamrock: 补充群成员信息area、age和level字段 2023-12-06 15:53:01 +08:00
ab6e431872 Merge remote-tracking branch 'origin/master' 2023-12-06 09:44:20 +08:00
9423df2670 Shamrock: 修复回复类型id错误 2023-12-06 09:44:10 +08:00
a2b3e42eee Shamrock: fix #125 2023-12-06 00:52:57 +08:00
d9a045bbf0 Shamrock: 修复构建 2023-12-05 18:26:15 +08:00
42ca17339e Shamrock: 不处理发给自己的消息 2023-12-05 18:24:22 +08:00
003c4d4456 Shamrock: 修复群戳一戳丶群打卡获取的群号错误 2023-12-04 19:42:46 +08:00
ec39aa7bc3 Shamrock: fix #124 2023-12-04 15:07:33 +08:00
4e3870a512 Shamrock: fix #124 2023-12-04 12:58:53 +08:00
97534b01a6 Merge pull request #121 from Miuzarte/patch-1
go-cqhttp like card and nickname field
2023-12-03 12:43:16 +08:00
61bed61bfb go-cqhttp like card and nickname field 2023-12-03 02:15:17 +08:00
a0ff4782db Merge remote-tracking branch 'origin/master' 2023-12-02 23:55:57 +08:00
41dd1de8f8 Shamrock: try fix #120 2023-12-02 23:55:48 +08:00
fa6634d6af Shamrock: fix #111 and add group sign event 2023-12-02 22:59:37 +08:00
60 changed files with 1485 additions and 471 deletions

View File

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

View File

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

View File

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

View File

@ -94,7 +94,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

View File

@ -1,12 +1,5 @@
import com.android.build.api.dsl.ApplicationExtension
fun gitCommitHash(): String {
val builder = ProcessBuilder("git", "rev-parse", "--short", "HEAD")
val process = builder.start()
val reader = process.inputReader()
val hash = reader.readText().trim()
return if (hash.isNotEmpty()) ".$hash" else ""
}
import java.io.ByteArrayOutputStream
plugins {
id("com.android.application")
@ -17,19 +10,20 @@ plugins {
android {
namespace = "moe.fuqiuluo.shamrock"
ndkVersion = "25.1.8937393"
compileSdk = 33
compileSdk = 34
defaultConfig {
applicationId = "moe.fuqiuluo.shamrock"
minSdk = 24
targetSdk = 33
versionCode = (System.currentTimeMillis() / 1000).toInt()
versionName = "1.0.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
@ -133,6 +126,11 @@ android {
}
configureAppSigningConfigsForRelease(project)
packagingOptions {
jniLibs {
useLegacyPackaging = true
}
}
}
fun configureAppSigningConfigsForRelease(project: Project) {
@ -154,10 +152,39 @@ fun configureAppSigningConfigsForRelease(project: Project) {
release {
signingConfig = signingConfigs.findByName("release")
}
debug {
signingConfig = signingConfigs.findByName("release")
}
}
}
}
fun getGitCommitCount(): Int {
val out = ByteArrayOutputStream()
exec {
commandLine("git", "rev-list", "--count", "HEAD")
standardOutput = out
}
return out.toString().trim().toInt()
}
fun getGitCommitHash(): String {
val out = ByteArrayOutputStream()
exec {
commandLine("git", "rev-parse", "--short", "HEAD")
standardOutput = out
}
return out.toString().trim()
}
fun getVersionCode(): Int {
return (System.currentTimeMillis() / 1000L).toInt()
}
fun getVersionName(): String {
return getGitCommitHash()
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")

View File

@ -3,10 +3,6 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".app.MyApplication"
@ -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"

View File

@ -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),
)
}

View File

@ -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}]。")

View File

@ -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",

View File

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

View File

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

Binary file not shown.

View File

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

3
gradlew vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 += ""

View File

@ -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");
@ -92,11 +93,27 @@ FILE* fake_fopen(const char *filename, const char *mode) {
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);
}
@ -120,4 +137,4 @@ Java_moe_fuqiuluo_shamrock_xposed_actions_AntiDetection_antiNativeDetections(JNI
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);
return true;
}
}

View File

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

View File

@ -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,9 @@ 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.getUin
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 +68,18 @@ 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.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
@ -420,6 +428,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 {
@ -526,7 +564,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
@ -805,12 +843,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 +860,14 @@ internal object GroupSvc: BaseSvc() {
}
}
private fun fromHtml(htmlString: String): String {
return HtmlCompat
// 特殊处理&#10;目的是替换为换行符否则会被fromHtml忽略并移除
.fromHtml(htmlString.replace("&#10;", "[shamrockplaceholder]"), HtmlCompat.FROM_HTML_MODE_LEGACY)
.toString()
.replace("[shamrockplaceholder]", "\n")
}
@OptIn(ExperimentalSerializationApi::class)
suspend fun uploadImageTroopNotice(image: String): Result<GroupAnnouncementMessageImage> {
val file = FileUtils.parseAndSave(image)
@ -907,4 +954,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(" ")}")
}
}
}

View File

@ -0,0 +1,8 @@
package moe.fuqiuluo.qqinterface.servlet
/**
* QQ收藏相关接口
*/
internal object QFavSvc: BaseSvc() {
}

View File

@ -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())
}

View File

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

View File

@ -331,7 +331,7 @@ internal sealed class MessageElemConverter: IMessageConvert {
MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
val notify = tip.xmlElement
when(notify.busiId) {
/* 群戳一戳 */1061L -> {}
/* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
}
}
@ -412,6 +412,22 @@ internal sealed class MessageElemConverter: IMessageConvert {
}
}
object MarkdownConverter: MessageElemConverter() {
override suspend fun convert(
chatType: Int,
peerId: String,
element: MsgElement
): MessageSegment {
val markdown = element.markdownElement
return MessageSegment(
type = "markdown",
data = mapOf(
"content" to markdown.content
)
)
}
}
protected fun unknownChatType(chatType: Int) {
throw UnsupportedOperationException("Not supported chat type: $chatType")
}

View File

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

View File

@ -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,7 @@ 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,
// MSG ACTIONS
SendMessage, DeleteMessage, GetMsg, GetForwardMsg, SendPrivateForwardMessage, SendGroupMessage, SendPrivateMessage,
@ -42,7 +42,7 @@ internal object ActionManager {
DeleteGroupFile, GetGroupFileSystemInfo, GetGroupRootFiles, GetGroupSubFiles,
GetGroupFileUrl, UploadPrivateFile,
//REQUEST ACTION
// REQUEST ACTION
SetFriendAddRequest, SetGroupAddRequest,
// GUILD

View File

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

View File

@ -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"
}

View File

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

View File

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

View File

@ -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")
@ -74,7 +59,7 @@ internal object SendForwardMessage : IActionHandler() {
suspend operator fun invoke(
chatType: Int,
peerId: String,
message: JsonArray,
messages: JsonArray,
echo: JsonElement = EmptyJsonString
): String {
kotlin.runCatching {
@ -83,63 +68,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 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 +167,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"
}

View File

@ -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"
}

View File

@ -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

View File

@ -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)

View File

@ -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)
}
}

View File

@ -140,4 +140,9 @@ 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)
}
}

View File

@ -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")

View File

@ -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)

View File

@ -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,14 @@ 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)
}
?: throw ErrorTokenException
if (accessToken.startsWith("Bearer ")) {
if (accessToken.startsWith("Bearer ", ignoreCase = true)) {
accessToken = accessToken.substring(7)
}
if (token != accessToken) {

View File

@ -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,6 +25,11 @@ 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.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() {
@ -66,65 +75,86 @@ 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 handleQuicklyActions(data: JsonObject) {
val action = data["action"].asString
val echo = data["echo"] ?: EmptyJsonString
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))
}
}
private suspend fun quicklyReply(
record: MsgRecord,
message: JsonArray,

View File

@ -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(",", "|", "")

View File

@ -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,
@ -84,7 +85,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
.ifBlank { record.sendRemarkName }
.ifBlank { record.sendMemberName }
.ifBlank { record.peerName },
card = record.sendMemberName.ifBlank { record.sendNickName },
card = record.sendMemberName,
role = when (record.senderUin) {
GroupSvc.getOwner(record.peerUin.toString()) -> MemberRole.Owner
in GroupSvc.getAdminList(record.peerUin.toString()) -> MemberRole.Admin
@ -106,7 +107,7 @@ 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
@ -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,

View File

@ -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)
}

View File

@ -106,7 +106,10 @@ internal abstract class WebSocketClientServlet(
}
private fun startHeartbeatTimer() {
if (heartbeatInterval <= 0) return
if (heartbeatInterval <= 0) {
LogCenter.log("被动WebSocket心跳间隔为0不启动心跳", Level.WARN)
return
}
timer(
name = "heartbeat",
initialDelay = 0,
@ -117,6 +120,7 @@ internal abstract class WebSocketClientServlet(
return@timer
}
val runtime = AppRuntimeFetcher.appRuntime
LogCenter.log("WebSocketClient心跳: ${app.longAccountUin}", Level.DEBUG)
send(
GlobalJson.encodeToString(
PushMetaEvent(

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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?,
)

View File

@ -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

View File

@ -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,
@ -87,6 +89,9 @@ internal data class NoticeEvent(
// 戳一戳
@SerialName("poke_detail") val pokeDetail: PokeDetail? = null,
// 群打卡
@SerialName("sign_detail") val signDetail: SignDetail? = null,
)
/**
@ -131,4 +136,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? = "",
)

View File

@ -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)
}
}

View File

@ -23,6 +23,7 @@ import moe.fuqiuluo.proto.asList
import moe.fuqiuluo.proto.asULong
import moe.fuqiuluo.qqinterface.servlet.FriendSvc.requestFriendSystemMsgNew
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
@ -63,28 +64,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 +158,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 +181,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,13 +199,18 @@ internal object PrimitiveListener {
}
private suspend fun onGroupTitleChange(msgTime: Long, pb: ProtoMap) {
val groupCode = pb[1, 1, 1].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]
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)
}
}
val targetUin = detail[5, 5].asLong
@ -212,13 +235,18 @@ internal object PrimitiveListener {
}
private suspend fun onEssenceMessage(msgTime: Long, pb: ProtoMap) {
val groupCode = pb[1, 1, 1].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]
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 groupId = detail[4].asLong
val mesSeq = detail[37].asInt
@ -254,31 +282,26 @@ 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)
}
}
readPacket.release()
lateinit var target: String
lateinit var operation: String
var action: String? = null
var suffix: String? = null
var actionImg: String? = null
var rankImg: String? = null
val groupCode = detail[4].asULong
detail[26][7]
.asList
.value
@ -287,18 +310,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[26][2].asInt) {
1061 -> {
LogCenter.log("群戳一戳($groupCode): $operation $action $target $suffix")
if (!GlobalEventTransmitter.GroupNoticeTransmitter
.transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupCode)
) {
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("群打卡($groupCode): $action $target")
if (!GlobalEventTransmitter.GroupNoticeTransmitter
.transGroupSign(time, target.toLong(), action, rankImg, groupCode)
) {
LogCenter.log("群打卡推送失败!", Level.WARN)
}
}
else -> {
LogCenter.log("onGroupPokeAndGroupSign unknown type ${detail[26, 2].asInt}", Level.WARN)
}
}
}
@ -347,8 +394,11 @@ 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 operation = try {
ContactHelper.getUinByUidAsync(pb[1, 3, 2, 5, 1, 1].asUtf8String).toLong()
} catch (e: Throwable) {
ContactHelper.getUinByUidAsync(pb[1, 3, 2, 5].asUtf8String).toLong()
}
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
val subtype = when (type) {
130 -> NoticeSubType.Leave
@ -390,7 +440,7 @@ internal object PrimitiveListener {
}
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
@ -406,17 +456,19 @@ internal object PrimitiveListener {
}
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)
}
}
val groupCode = detail[4].asULong
val operatorUid = detail[11, 1].asUtf8String
val targetUid = detail[11, 3, 6].asUtf8String
val msgSeq = detail[11, 3, 1].asLong
@ -436,9 +488,6 @@ internal object PrimitiveListener {
) {
LogCenter.log("群消息撤回推送失败!", Level.WARN)
}
} finally {
readPacket.release()
}
}
private suspend fun onGroupApply(time: Long, pb: ProtoMap) {
@ -448,16 +497,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 +524,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 +534,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,15 +563,15 @@ 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)

View File

@ -37,6 +37,8 @@ annotation class ShamrockDsl
private val keyIsJson = AttributeKey<Boolean>("isJson")
private val keyJsonObject = AttributeKey<JsonObject>("paramsJson")
private val keyJsonArray = AttributeKey<JsonArray>("paramsJsonArray")
private val keyJsonElement = AttributeKey<JsonElement>("paramsJsonElement")
private val keyParts = AttributeKey<Parameters>("paramsParts")
suspend fun ApplicationCall.fetch(key: String): String {
@ -167,7 +169,9 @@ suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostOrThrow(key: String)
}
fun PipelineContext<Unit, ApplicationCall>.isJsonData(): Boolean {
return ContentType.Application.Json == call.request.contentType() || (keyIsJson in call.attributes && call.attributes[keyIsJson])
return ContentType.Application.Json == call.request.contentType()
|| (keyIsJson in call.attributes && call.attributes[keyIsJson])
|| (keyJsonElement in call.attributes)
}
suspend fun PipelineContext<Unit, ApplicationCall>.isJsonString(key: String): Boolean {
@ -245,12 +249,33 @@ suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonObjectOrNull(key
call.attributes[keyJsonObject]
} else {
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
call.attributes.put(keyIsJson, true)
call.attributes.put(keyJsonObject, it)
}
}
return data[key].asJsonObjectOrNull
}
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonElementOrNull(): JsonElement? {
return runCatching {
if (call.attributes.contains(keyJsonObject)) {
call.attributes[keyJsonObject]
} else if (call.attributes.contains(keyJsonArray)) {
call.attributes[keyJsonArray]
} else if (call.attributes.contains(keyJsonElement)) {
call.attributes[keyJsonElement]
} else {
Json.parseToJsonElement(call.receiveText()).also {
call.attributes.put(keyJsonElement, it)
if (it is JsonObject) {
call.attributes.put(keyJsonObject, it)
} else if (it is JsonArray) {
call.attributes.put(keyJsonArray, it)
}
}
}
}.getOrNull()
}
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonArray(key: String): JsonArray {
val data = if (call.attributes.contains(keyJsonObject)) {

View File

@ -66,6 +66,8 @@ class AntiDetection: IAction {
}
private fun antiFindPackage(context: Context) {
if (isAntiFindPackage) return
val packageManager = context.packageManager
val applicationInfo = packageManager.getApplicationInfo("moe.fuqiuluo.shamrock", 0)
val packageInfo = packageManager.getPackageInfo("moe.fuqiuluo.shamrock", 0)
@ -74,7 +76,7 @@ class AntiDetection: IAction {
val packageName = it.args[0] as String
if(packageName == "moe.fuqiuluo.shamrock") {
LogCenter.log("AntiDetection: 检测到对Shamrock的检测欺骗PackageManager(GA)", Level.WARN)
it.throwable = PackageManager.NameNotFoundException()
it.throwable = PackageManager.NameNotFoundException("Hided")
} else if (packageName == "moe.fuqiuluo.shamrock.hided") {
it.result = applicationInfo
}
@ -100,6 +102,8 @@ class AntiDetection: IAction {
}
}
}
isAntiFindPackage = true
}
private fun antiMemoryWalking() {
@ -205,4 +209,9 @@ class AntiDetection: IAction {
}.toTypedArray()
}
}
companion object {
@JvmStatic
var isAntiFindPackage = false
}
}

View File

@ -65,10 +65,12 @@ internal class InitRemoteService : IAction {
if (token.isNotBlank()) {
wsHeaders["authorization"] = "bearer $token"
}
LogCenter.log("尝试接WebSocketClient(url = ${conn.address})",Level.WARN)
LogCenter.log("尝试接WebSocketClient(url = ${conn.address})",Level.WARN)
startWebSocketClient(conn.address, conn.heartbeatInterval ?: 15000, wsHeaders)
}
}
} else {
LogCenter.log("未启用被动WebSocket不会加载连接。")
}
}

View File

@ -39,10 +39,6 @@ internal object KeepAlive {
}
DeviceIdleController.hookMethod("onStart").after {
XposedBridge.log("[Shamrock] DeviceIdleController onStart")
timer(initialDelay = 120_000L, period = 240_000L) {
XposedBridge.log("[Shamrock] try to wakeup screen")
becomeActiveLocked.invoke(it.thisObject, "screen", Process.myUid())
}
}
DeviceIdleController.hookMethod("becomeInactiveIfAppropriateLocked").before {
XposedBridge.log("[Shamrock] DeviceIdleController becomeInactiveIfAppropriateLocked")