Merge branch 'dev' into android_target

# Conflicts:
#	build.gradle.kts
#	buildSrc/src/main/kotlin/Versions.kt
This commit is contained in:
Him188 2021-02-24 09:20:47 +08:00
commit 0b971b2117
132 changed files with 4873 additions and 2211 deletions

View File

@ -1,123 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: Bintray Publish
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
release:
types: [ created, prereleased ]
push:
tags:
- '*-dev*'
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
publish-mirai:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Checkout submodules
run: git submodule update --init --recursive --remote
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: chmod -R 777 *
run: chmod -R 777 *
- name: Init gradle project
run: ./gradlew clean --info
- name: Check keys
run: >
./gradlew :mirai-core-utils:ensureBintrayAvailable
:mirai-core-api:ensureBintrayAvailable
:mirai-core:ensureBintrayAvailable
:mirai-console:ensureBintrayAvailable
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: fillBuildConstants
run: >
./gradlew
fillBuildConstants --info --stacktrace
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Assemble
run: ./gradlew assemble --info --stacktrace
- name: Check
run: ./gradlew check --info --stacktrace
- name: Gradle :mirai-core-utils:publish
run: >
./gradlew :mirai-core-utils:publish --info --stacktrace
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core-api:publish
run: >
./gradlew :mirai-core-api:publish --info --stacktrace
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core:publish
run: >
./gradlew :mirai-core:publish --info --stacktrace
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core-all:bintrayUpload
run: >
./gradlew :mirai-core-all:bintrayUpload --info
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console:bintrayUpload
run: >
./gradlew
:mirai-console:bintrayUpload --info
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console-terminal:bintrayUpload
run: >
./gradlew
:mirai-console-terminal:bintrayUpload --info
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console-compiler-common:bintrayUpload
run: >
./gradlew
:mirai-console-compiler-common:bintrayUpload --info
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console-compiler-annotations:bintrayUpload
run: >
./gradlew
:mirai-console-compiler-annotations:bintrayUpload --info
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-console-intellij:bintrayUpload
run: >
./gradlew
:mirai-console-intellij:bintrayUpload --info
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Publish Gradle plugin
run: >
./gradlew
:mirai-console-gradle:publishPlugins --info --stacktrace
-Dgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }}
-Dgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }}

View File

@ -19,13 +19,13 @@ jobs:
run: chmod -R 777 *
- name: Init gradle project
run: ./gradlew clean --info --stacktrace
run: ./gradlew clean --scan
- name: Build mirai-core series
run: ./gradlew assemble --info --stacktrace
run: ./gradlew assemble --scan
- name: mirai-core Tests
run: ./gradlew check --info --stacktrace
run: ./gradlew check --scan
build-all:
runs-on: ubuntu-latest
@ -45,10 +45,10 @@ jobs:
run: chmod -R 777 *
- name: Init gradle project
run: ./gradlew clean --info --stacktrace
run: ./gradlew clean --scan
- name: Build all
run: ./gradlew assemble --info --stacktrace
run: ./gradlew assemble --scan
- name: All Tests
run: ./gradlew check --info --stacktrace
run: ./gradlew check --scan

117
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,117 @@
# This is a basic workflow to help you get started with Actions
name: Release Publish
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
release:
types: [ created ]
push:
tags:
- '*-dev*'
- '*-release'
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
publish-mirai:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Checkout submodules
run: git submodule update --init --recursive --remote
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: chmod -R 777 *
run: chmod -R 777 *
- name: Keys setup
shell: bash
run: |
mkdir build-gpg-sign
echo "$GPG_PRIVATE" > build-gpg-sign/keys.gpg
echo "$GPG_PUBLIC_" > build-gpg-sign/keys.gpg.pub
mkdir build-secret-keys
echo "$SONATYPE_USER" > build-secret-keys/sonatype.key
echo "$SONATYPE_KEY" >> build-secret-keys/sonatype.key
echo "$BINTRAY_USER" > build-secret-keys/bintray.key
echo "$BINTRAY_KEY" >> build-secret-keys/bintray.key
env:
GPG_PRIVATE: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PUBLIC_: ${{ secrets.GPG_PUBLIC_KEY }}
SONATYPE_USER: ${{ secrets.SONATYPE_USER }}
SONATYPE_KEY: ${{ secrets.SONATYPE_KEY }}
BINTRAY_USER: ${{ secrets.BINTRAY_USER }}
BINTRAY_KEY: ${{ secrets.BINTRAY_KEY }}
- name: Init gradle project
run: ./gradlew clean --scan
- name: Check keys
run: ./gradlew ensureBintrayAvailable ensureMavenCentralAvailable
- name: fillBuildConstants
run: >
./gradlew
fillBuildConstants --scan
- name: Assemble
run: ./gradlew assemble --scan
- name: Check
run: ./gradlew check --scan
- name: Gradle :mirai-core-utils:publish
run: >
./gradlew :mirai-core-utils:publish --scan
- name: Gradle :mirai-core-api:publish
run: >
./gradlew :mirai-core-api:publish --scan
- name: Gradle :mirai-core:publish
run: >
./gradlew :mirai-core:publish --scan
- name: Gradle :mirai-core-all:publish
run: >
./gradlew :mirai-core-all:publish --info
- name: Gradle :mirai-console:publish
run: >
./gradlew
:mirai-console:publish --info
- name: Gradle :mirai-console-terminal:publish
run: >
./gradlew
:mirai-console-terminal:publish --info
- name: Gradle :mirai-console-compiler-common:publish
run: >
./gradlew
:mirai-console-compiler-common:publish --info
- name: Gradle :mirai-console-compiler-annotations:publish
run: >
./gradlew
:mirai-console-compiler-annotations:publish --info
- name: Publish Gradle plugin
run: >
./gradlew
:mirai-console-gradle:publishPlugins --scan
-Dgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }}
-Dgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }}
- name: Gradle :ci-release-helper:closeAndReleaseRepository
run: >
./gradlew
:ci-release-helper:closeAndReleaseRepository --info

7
.gitignore vendored
View File

@ -45,4 +45,9 @@ keys.properties
token.txt
bintray.user.txt
bintray.key.txt
bintray.key.txt
# For gpg sign
/build-gpg-sign
# Name for IDEA direction sorting
build-secret-keys/

View File

@ -1,3 +1,7 @@
<component name="CopyrightManager">
<settings default="Mamoe/mirai" />
<settings default="Mamoe/mirai">
<module2copyright>
<element module="Production" copyright="Mamoe/mirai" />
</module2copyright>
</settings>
</component>

View File

@ -85,6 +85,7 @@ mirai 是一个在全平台下运行,提供 QQ Android 协议支持的高效
- 闪照
- 撤回群员消息
- 自定义消息
- 音乐分享
**群相关**
- 群列表

View File

@ -89,21 +89,16 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve
public fun calculateGroupUinByGroupCode (J)J
public abstract fun constructMessageSource (JLnet/mamoe/mirai/message/data/MessageSourceKind;JJ[II[ILnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/OfflineMessageSource;
public abstract fun createImage (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image;
public synthetic fun deleteGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLjava/lang/String;)Lkotlin/Unit;
public fun deleteGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLjava/lang/String;)V
public fun downloadForwardMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;)Ljava/util/List;
public abstract fun downloadForwardMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun downloadLongMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain;
public abstract fun downloadLongMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun getBotFactory ()Lnet/mamoe/mirai/BotFactory;
public abstract fun getFileCacheStrategy ()Lnet/mamoe/mirai/utils/FileCacheStrategy;
public fun getGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLjava/lang/String;)Lnet/mamoe/mirai/data/GroupAnnouncement;
public fun getGroupVoiceDownloadUrl (Lnet/mamoe/mirai/Bot;[BJJ)Ljava/lang/String;
public abstract fun getHttp ()Lio/ktor/client/HttpClient;
public fun getOnlineOtherClientsList (Lnet/mamoe/mirai/Bot;Z)Ljava/util/List;
public abstract fun getOnlineOtherClientsList (Lnet/mamoe/mirai/Bot;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun getOnlineOtherClientsList$default (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/Bot;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public fun getRawGroupActiveData (Lnet/mamoe/mirai/Bot;JI)Lnet/mamoe/mirai/data/GroupActiveData;
public fun getRawGroupAnnouncements (Lnet/mamoe/mirai/Bot;JII)Lnet/mamoe/mirai/data/GroupAnnouncementList;
public fun getRawGroupHonorListData (Lnet/mamoe/mirai/Bot;JLnet/mamoe/mirai/data/GroupHonorType;)Lnet/mamoe/mirai/data/GroupHonorListData;
public fun getRawGroupList (Lnet/mamoe/mirai/Bot;)Lkotlin/sequences/Sequence;
public fun getRawGroupMemberList (Lnet/mamoe/mirai/Bot;JJJ)Lkotlin/sequences/Sequence;
public fun getUin (Lnet/mamoe/mirai/contact/ContactOrBot;)J
public synthetic fun ignoreInvitedJoinGroupRequest (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;)Lkotlin/Unit;
public fun ignoreInvitedJoinGroupRequest (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;)V
@ -112,15 +107,10 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve
public fun ignoreMemberJoinRequest (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;Z)V
public abstract fun ignoreMemberJoinRequest (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun ignoreMemberJoinRequest$default (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public synthetic fun muteAnonymousMember (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;JI)Lkotlin/Unit;
public fun muteAnonymousMember (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;JI)V
public fun queryImageUrl (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/Image;)Ljava/lang/String;
public abstract fun queryImageUrl (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/Image;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun queryProfile (Lnet/mamoe/mirai/Bot;J)Lnet/mamoe/mirai/data/UserProfile;
public abstract fun queryProfile (Lnet/mamoe/mirai/Bot;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun recallFriendMessageRaw (Lnet/mamoe/mirai/Bot;J[I[II)Z
public fun recallGroupMessageRaw (Lnet/mamoe/mirai/Bot;J[I[I)Z
public fun recallGroupTempMessageRaw (Lnet/mamoe/mirai/Bot;JJ[I[II)Z
public synthetic fun recallMessage (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/MessageSource;)Lkotlin/Unit;
public fun recallMessage (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/MessageSource;)V
public abstract fun recallMessage (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@ -132,17 +122,10 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve
public fun rejectNewFriendRequest (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;Z)V
public abstract fun rejectNewFriendRequest (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun rejectNewFriendRequest$default (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public fun sendGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLnet/mamoe/mirai/data/GroupAnnouncement;)Ljava/lang/String;
public fun sendNudge (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/action/Nudge;Lnet/mamoe/mirai/contact/Contact;)Z
public abstract fun sendNudge (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/action/Nudge;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun setFileCacheStrategy (Lnet/mamoe/mirai/utils/FileCacheStrategy;)V
public abstract fun setHttp (Lio/ktor/client/HttpClient;)V
public synthetic fun solveBotInvitedJoinGroupRequestEvent (Lnet/mamoe/mirai/Bot;JJJZ)Lkotlin/Unit;
public fun solveBotInvitedJoinGroupRequestEvent (Lnet/mamoe/mirai/Bot;JJJZ)V
public synthetic fun solveMemberJoinRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;JLjava/lang/Boolean;ZLjava/lang/String;)Lkotlin/Unit;
public fun solveMemberJoinRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;JLjava/lang/Boolean;ZLjava/lang/String;)V
public synthetic fun solveNewFriendRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;ZZ)Lkotlin/Unit;
public fun solveNewFriendRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;ZZ)V
}
public abstract interface annotation class net/mamoe/mirai/LowLevelApi : java/lang/annotation/Annotation {
@ -179,6 +162,8 @@ public abstract interface class net/mamoe/mirai/LowLevelApiAccessor {
public abstract fun recallGroupMessageRaw (Lnet/mamoe/mirai/Bot;J[I[ILkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun recallGroupTempMessageRaw (Lnet/mamoe/mirai/Bot;JJ[I[II)Z
public abstract fun recallGroupTempMessageRaw (Lnet/mamoe/mirai/Bot;JJ[I[IILkotlin/coroutines/Continuation;)Ljava/lang/Object;
public synthetic fun refreshKeys (Lnet/mamoe/mirai/Bot;)Lkotlin/Unit;
public fun refreshKeys (Lnet/mamoe/mirai/Bot;)V
public abstract fun refreshKeys (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun sendGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLnet/mamoe/mirai/data/GroupAnnouncement;)Ljava/lang/String;
public abstract fun sendGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLnet/mamoe/mirai/data/GroupAnnouncement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@ -201,12 +186,9 @@ public final class net/mamoe/mirai/Mirai {
public abstract interface class net/mamoe/mirai/contact/AnonymousMember : net/mamoe/mirai/contact/Member {
public abstract fun getAnonymousId ()Ljava/lang/String;
public synthetic fun mute (I)Lkotlin/Unit;
public fun mute (I)V
public fun nudge ()Lnet/mamoe/mirai/message/action/MemberNudge;
public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/Nudge;
public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/UserNudge;
public fun queryProfile ()Lnet/mamoe/mirai/data/UserProfile;
public fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@ -369,12 +351,10 @@ public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/corouti
public fun nudge ()Lnet/mamoe/mirai/message/action/FriendNudge;
public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/Nudge;
public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/UserNudge;
public fun queryProfile ()Lnet/mamoe/mirai/data/UserProfile;
public fun sendMessage (Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt;
public fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/MessageReceipt;
public abstract fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image;
}
public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/Contact {
@ -401,7 +381,6 @@ public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutin
public fun setEssenceMessage (Lnet/mamoe/mirai/message/data/MessageSource;)Z
public abstract fun setEssenceMessage (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun setName (Ljava/lang/String;)V
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image;
public fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Voice;
public abstract fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
@ -437,10 +416,10 @@ public abstract interface class net/mamoe/mirai/contact/Member : net/mamoe/mirai
public fun mute (I)V
public abstract fun mute (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun nudge ()Lnet/mamoe/mirai/message/action/MemberNudge;
public fun queryProfile ()Lnet/mamoe/mirai/data/UserProfile;
public fun sendMessage (Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt;
public abstract fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/MessageReceipt;
public abstract fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image;
}
public final class net/mamoe/mirai/contact/MemberKt {
@ -486,12 +465,9 @@ public abstract interface class net/mamoe/mirai/contact/NormalMember : net/mamoe
public synthetic fun kick (Ljava/lang/String;)Lkotlin/Unit;
public fun kick (Ljava/lang/String;)V
public abstract fun kick (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public synthetic fun mute (I)Lkotlin/Unit;
public fun mute (I)V
public fun nudge ()Lnet/mamoe/mirai/message/action/MemberNudge;
public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/Nudge;
public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/UserNudge;
public fun queryProfile ()Lnet/mamoe/mirai/data/UserProfile;
public fun sendMessage (Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt;
public fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/MessageReceipt;
@ -501,7 +477,6 @@ public abstract interface class net/mamoe/mirai/contact/NormalMember : net/mamoe
public synthetic fun unmute ()Lkotlin/Unit;
public fun unmute ()V
public abstract fun unmute (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image;
}
public final class net/mamoe/mirai/contact/NormalMemberKt {
@ -514,7 +489,6 @@ public abstract interface class net/mamoe/mirai/contact/OtherClient : net/mamoe/
public abstract fun getBot ()Lnet/mamoe/mirai/Bot;
public fun getId ()J
public abstract fun getInfo ()Lnet/mamoe/mirai/contact/OtherClientInfo;
public fun sendMessage (Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt;
public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
@ -571,12 +545,10 @@ public abstract interface class net/mamoe/mirai/contact/Stranger : kotlinx/corou
public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/Nudge;
public fun nudge ()Lnet/mamoe/mirai/message/action/StrangerNudge;
public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/UserNudge;
public fun queryProfile ()Lnet/mamoe/mirai/data/UserProfile;
public fun sendMessage (Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt;
public fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/MessageReceipt;
public abstract fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image;
}
public final class net/mamoe/mirai/contact/StrangerKt {
@ -585,10 +557,6 @@ public final class net/mamoe/mirai/contact/StrangerKt {
}
public abstract interface class net/mamoe/mirai/contact/TempUser : net/mamoe/mirai/contact/User {
public fun queryProfile ()Lnet/mamoe/mirai/data/UserProfile;
public fun sendMessage (Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt;
public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/MessageReceipt;
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image;
}
public abstract interface class net/mamoe/mirai/contact/User : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/Contact, net/mamoe/mirai/contact/UserOrBot {
@ -602,7 +570,6 @@ public abstract interface class net/mamoe/mirai/contact/User : kotlinx/coroutine
public fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/MessageReceipt;
public abstract fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image;
}
public final class net/mamoe/mirai/contact/UserKt {
@ -619,6 +586,7 @@ public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mira
public abstract fun getNick ()Ljava/lang/String;
public abstract fun getRemark ()Ljava/lang/String;
public abstract fun getUin ()J
public abstract fun setRemark (Ljava/lang/String;)V
}
public class net/mamoe/mirai/data/FriendInfoImpl : net/mamoe/mirai/data/FriendInfo {
@ -1910,7 +1878,7 @@ public final class net/mamoe/mirai/event/events/BotEventsKt {
public static final synthetic fun isSuccess (Lnet/mamoe/mirai/event/events/MessagePostSendEvent;)Z
}
public final class net/mamoe/mirai/event/events/BotGroupPermissionChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/BotGroupPermissionChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/MemberPermission;)V
public final fun component1 ()Lnet/mamoe/mirai/contact/Group;
public final fun component2 ()Lnet/mamoe/mirai/contact/MemberPermission;
@ -1925,7 +1893,7 @@ public final class net/mamoe/mirai/event/events/BotGroupPermissionChangeEvent :
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BaseGroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/Bot;JJJLjava/lang/String;Ljava/lang/String;)V
public final synthetic fun accept ()Lkotlin/Unit;
public final fun accept ()V
@ -1941,7 +1909,7 @@ public final class net/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent
public fun equals (Ljava/lang/Object;)Z
public fun getBot ()Lnet/mamoe/mirai/Bot;
public final fun getEventId ()J
public final fun getGroupId ()J
public fun getGroupId ()J
public final fun getGroupName ()Ljava/lang/String;
public final fun getInvitor ()Lnet/mamoe/mirai/contact/Friend;
public final fun getInvitorId ()J
@ -1953,7 +1921,7 @@ public final class net/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent
public fun toString ()Ljava/lang/String;
}
public abstract class net/mamoe/mirai/event/events/BotJoinGroupEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/internal/network/Packet {
public abstract class net/mamoe/mirai/event/events/BotJoinGroupEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group;
}
@ -1991,7 +1959,7 @@ public final class net/mamoe/mirai/event/events/BotJoinGroupEvent$Retrieve : net
public fun toString ()Ljava/lang/String;
}
public abstract class net/mamoe/mirai/event/events/BotLeaveEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet {
public abstract class net/mamoe/mirai/event/events/BotLeaveEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun getBot ()Lnet/mamoe/mirai/Bot;
public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group;
}
@ -2021,7 +1989,7 @@ public final class net/mamoe/mirai/event/events/BotLeaveEvent$Kick : net/mamoe/m
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/BotMuteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/BotMuteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (ILnet/mamoe/mirai/contact/NormalMember;)V
public final fun component1 ()I
public final fun component2 ()Lnet/mamoe/mirai/contact/NormalMember;
@ -2073,6 +2041,7 @@ public abstract interface class net/mamoe/mirai/event/events/BotOfflineEvent$Cau
}
public final class net/mamoe/mirai/event/events/BotOfflineEvent$Dropped : net/mamoe/mirai/event/events/BotOfflineEvent, net/mamoe/mirai/event/events/BotOfflineEvent$CauseAware, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;)V
public final fun component1 ()Lnet/mamoe/mirai/Bot;
public final fun component2 ()Ljava/lang/Throwable;
public final fun copy (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$Dropped;
@ -2087,6 +2056,7 @@ public final class net/mamoe/mirai/event/events/BotOfflineEvent$Dropped : net/ma
}
public final class net/mamoe/mirai/event/events/BotOfflineEvent$Force : net/mamoe/mirai/event/events/BotOfflineEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;)V
public final fun component1 ()Lnet/mamoe/mirai/Bot;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
@ -2103,6 +2073,7 @@ public final class net/mamoe/mirai/event/events/BotOfflineEvent$Force : net/mamo
}
public final class net/mamoe/mirai/event/events/BotOfflineEvent$MsfOffline : net/mamoe/mirai/event/events/BotOfflineEvent, net/mamoe/mirai/event/events/BotOfflineEvent$CauseAware, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;)V
public final fun component1 ()Lnet/mamoe/mirai/Bot;
public final fun component2 ()Ljava/lang/Throwable;
public final fun copy (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$MsfOffline;
@ -2117,6 +2088,7 @@ public final class net/mamoe/mirai/event/events/BotOfflineEvent$MsfOffline : net
}
public final class net/mamoe/mirai/event/events/BotOfflineEvent$PacketFactoryErrorCode : net/mamoe/mirai/event/events/BotOfflineEvent, net/mamoe/mirai/event/events/BotOfflineEvent$CauseAware, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (ILnet/mamoe/mirai/Bot;Ljava/lang/Throwable;)V
public final fun component1 ()I
public final fun component2 ()Lnet/mamoe/mirai/Bot;
public final fun component3 ()Ljava/lang/Throwable;
@ -2133,6 +2105,7 @@ public final class net/mamoe/mirai/event/events/BotOfflineEvent$PacketFactoryErr
}
public final class net/mamoe/mirai/event/events/BotOfflineEvent$RequireReconnect : net/mamoe/mirai/event/events/BotOfflineEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/Bot;)V
public final fun component1 ()Lnet/mamoe/mirai/Bot;
public final fun copy (Lnet/mamoe/mirai/Bot;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$RequireReconnect;
public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotOfflineEvent$RequireReconnect;Lnet/mamoe/mirai/Bot;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$RequireReconnect;
@ -2145,6 +2118,7 @@ public final class net/mamoe/mirai/event/events/BotOfflineEvent$RequireReconnect
}
public final class net/mamoe/mirai/event/events/BotOnlineEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent {
public fun <init> (Lnet/mamoe/mirai/Bot;)V
public final fun component1 ()Lnet/mamoe/mirai/Bot;
public final fun copy (Lnet/mamoe/mirai/Bot;)Lnet/mamoe/mirai/event/events/BotOnlineEvent;
public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotOnlineEvent;Lnet/mamoe/mirai/Bot;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotOnlineEvent;
@ -2158,6 +2132,7 @@ public abstract interface class net/mamoe/mirai/event/events/BotPassiveEvent : n
}
public final class net/mamoe/mirai/event/events/BotReloginEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent {
public fun <init> (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;)V
public final fun component1 ()Lnet/mamoe/mirai/Bot;
public final fun component2 ()Ljava/lang/Throwable;
public final fun copy (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;)Lnet/mamoe/mirai/event/events/BotReloginEvent;
@ -2169,7 +2144,7 @@ public final class net/mamoe/mirai/event/events/BotReloginEvent : net/mamoe/mira
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/BotUnmuteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/BotUnmuteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/contact/NormalMember;)V
public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember;
public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/BotUnmuteEvent;
@ -2188,7 +2163,7 @@ public final class net/mamoe/mirai/event/events/EventCancelledException : java/l
public fun <init> (Ljava/lang/Throwable;)V
}
public final class net/mamoe/mirai/event/events/FriendAddEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/FriendAddEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/event/events/FriendInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/contact/Friend;)V
public final fun component1 ()Lnet/mamoe/mirai/contact/Friend;
public final fun copy (Lnet/mamoe/mirai/contact/Friend;)Lnet/mamoe/mirai/event/events/FriendAddEvent;
@ -2209,7 +2184,7 @@ public final class net/mamoe/mirai/event/events/FriendAvatarChangedEvent : net/m
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/FriendDeleteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/FriendDeleteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/event/events/FriendInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public final fun component1 ()Lnet/mamoe/mirai/contact/Friend;
public final fun copy (Lnet/mamoe/mirai/contact/Friend;)Lnet/mamoe/mirai/event/events/FriendDeleteEvent;
public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendDeleteEvent;Lnet/mamoe/mirai/contact/Friend;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendDeleteEvent;
@ -2304,7 +2279,7 @@ public final class net/mamoe/mirai/event/events/FriendMessageSyncEvent : net/mam
public fun getTime ()I
}
public final class net/mamoe/mirai/event/events/FriendNickChangedEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/FriendNickChangedEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/event/events/FriendInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public final fun component1 ()Lnet/mamoe/mirai/contact/Friend;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
@ -2318,7 +2293,7 @@ public final class net/mamoe/mirai/event/events/FriendNickChangedEvent : net/mam
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/FriendRemarkChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/FriendRemarkChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/event/events/FriendInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public final fun component1 ()Lnet/mamoe/mirai/contact/Friend;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
@ -2332,7 +2307,7 @@ public final class net/mamoe/mirai/event/events/FriendRemarkChangeEvent : net/ma
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/GroupAllowAnonymousChatEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/GroupAllowAnonymousChatEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)V
public final fun component1 ()Z
public final fun component2 ()Z
@ -2352,7 +2327,7 @@ public final class net/mamoe/mirai/event/events/GroupAllowAnonymousChatEvent : n
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/GroupAllowConfessTalkEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/GroupAllowConfessTalkEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (ZZLnet/mamoe/mirai/contact/Group;Z)V
public final fun component1 ()Z
public final fun component2 ()Z
@ -2371,7 +2346,7 @@ public final class net/mamoe/mirai/event/events/GroupAllowConfessTalkEvent : net
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/GroupAllowMemberInviteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/GroupAllowMemberInviteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)V
public final fun component1 ()Z
public final fun component2 ()Z
@ -2395,7 +2370,7 @@ public abstract interface class net/mamoe/mirai/event/events/GroupAwareMessageEv
public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group;
}
public final class net/mamoe/mirai/event/events/GroupEntranceAnnouncementChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/GroupEntranceAnnouncementChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
@ -2493,7 +2468,7 @@ public final class net/mamoe/mirai/event/events/GroupMessageSyncEvent : net/mamo
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/GroupMuteAllEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/GroupMuteAllEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)V
public final fun component1 ()Z
public final fun component2 ()Z
@ -2513,7 +2488,7 @@ public final class net/mamoe/mirai/event/events/GroupMuteAllEvent : net/mamoe/mi
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/GroupNameChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/GroupNameChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
@ -2666,7 +2641,7 @@ public final class net/mamoe/mirai/event/events/ImageUploadEvent$Succeed : net/m
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/MemberCardChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/MemberCardChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/NormalMember;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
@ -2715,7 +2690,7 @@ public final class net/mamoe/mirai/event/events/MemberHonorChangeEvent$Lose : ne
public fun toString ()Ljava/lang/String;
}
public abstract class net/mamoe/mirai/event/events/MemberJoinEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/internal/network/Packet {
public abstract class net/mamoe/mirai/event/events/MemberJoinEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public synthetic fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member;
public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember;
@ -2759,7 +2734,7 @@ public final class net/mamoe/mirai/event/events/MemberJoinEvent$Retrieve : net/m
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/MemberJoinRequestEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/MemberJoinRequestEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BaseGroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet {
public static final field Companion Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent$Companion;
public synthetic fun <init> (Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;)V
public fun <init> (Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/Long;)V
@ -2785,7 +2760,7 @@ public final class net/mamoe/mirai/event/events/MemberJoinRequestEvent : net/mam
public final fun getFromId ()J
public final fun getFromNick ()Ljava/lang/String;
public final fun getGroup ()Lnet/mamoe/mirai/contact/Group;
public final fun getGroupId ()J
public fun getGroupId ()J
public final fun getGroupName ()Ljava/lang/String;
public final fun getInvitor ()Lnet/mamoe/mirai/contact/NormalMember;
public final fun getInvitorId ()Ljava/lang/Long;
@ -2808,7 +2783,7 @@ public final class net/mamoe/mirai/event/events/MemberJoinRequestEvent : net/mam
public fun toString ()Ljava/lang/String;
}
public abstract class net/mamoe/mirai/event/events/MemberLeaveEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberEvent {
public abstract class net/mamoe/mirai/event/events/MemberLeaveEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent {
}
public final class net/mamoe/mirai/event/events/MemberLeaveEvent$Kick : net/mamoe/mirai/event/events/MemberLeaveEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet {
@ -2838,7 +2813,7 @@ public final class net/mamoe/mirai/event/events/MemberLeaveEvent$Quit : net/mamo
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/MemberMuteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/MemberMuteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/contact/Member;ILnet/mamoe/mirai/contact/Member;)V
public final fun component1 ()Lnet/mamoe/mirai/contact/Member;
public final fun component2 ()I
@ -2853,7 +2828,7 @@ public final class net/mamoe/mirai/event/events/MemberMuteEvent : net/mamoe/mira
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/MemberPermissionChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/MemberPermissionChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/MemberPermission;)V
public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember;
public final fun component2 ()Lnet/mamoe/mirai/contact/MemberPermission;
@ -2869,7 +2844,7 @@ public final class net/mamoe/mirai/event/events/MemberPermissionChangeEvent : ne
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/MemberSpecialTitleChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/event/events/GroupOperableEvent {
public final class net/mamoe/mirai/event/events/MemberSpecialTitleChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
@ -2888,7 +2863,7 @@ public final class net/mamoe/mirai/event/events/MemberSpecialTitleChangeEvent :
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/event/events/MemberUnmuteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/MemberUnmuteEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/GroupMemberEvent, net/mamoe/mirai/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet {
public fun <init> (Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/contact/Member;)V
public final fun component1 ()Lnet/mamoe/mirai/contact/Member;
public final fun component2 ()Lnet/mamoe/mirai/contact/Member;
@ -2987,7 +2962,7 @@ public final class net/mamoe/mirai/event/events/MessageRecallEvent$GroupRecall :
public abstract interface class net/mamoe/mirai/event/events/MessageSyncEvent : net/mamoe/mirai/event/events/MessageEvent {
}
public final class net/mamoe/mirai/event/events/NewFriendRequestEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet {
public final class net/mamoe/mirai/event/events/NewFriendRequestEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/event/events/FriendInfoChangeEvent, net/mamoe/mirai/internal/network/Packet {
public final synthetic fun accept ()Lkotlin/Unit;
public final fun accept ()V
public final fun accept (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@ -3500,6 +3475,41 @@ public final class net/mamoe/mirai/message/data/CustomMessageMetadata$Companion
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Dice : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MarketFace {
public static final field Key Lnet/mamoe/mirai/message/data/Dice$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public fun <init> (I)V
public synthetic fun <init> (IILkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun appendMiraiCodeTo (Ljava/lang/StringBuilder;)V
public final fun component1 ()I
public final fun copy (I)Lnet/mamoe/mirai/message/data/Dice;
public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/Dice;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Dice;
public fun equals (Ljava/lang/Object;)Z
public fun getId ()I
public fun getName ()Ljava/lang/String;
public final fun getValue ()I
public fun hashCode ()I
public static final fun random ()Lnet/mamoe/mirai/message/data/Dice;
public fun toString ()Ljava/lang/String;
public static final fun write$Self (Lnet/mamoe/mirai/message/data/Dice;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class net/mamoe/mirai/message/data/Dice$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/Dice$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Dice;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/Dice;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Dice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public final fun random ()Lnet/mamoe/mirai/message/data/Dice;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/EmptyMessageChain : java/util/List, kotlin/jvm/internal/markers/KMappedMarker, net/mamoe/mirai/message/data/MessageChain {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/EmptyMessageChain;
public synthetic fun add (ILjava/lang/Object;)V
@ -4696,11 +4706,13 @@ public final class net/mamoe/mirai/message/data/MusicKind : java/lang/Enum {
public static fun values ()[Lnet/mamoe/mirai/message/data/MusicKind;
}
public final class net/mamoe/mirai/message/data/MusicShare : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
public final class net/mamoe/mirai/message/data/MusicShare : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
public static final field Key Lnet/mamoe/mirai/message/data/MusicShare$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public synthetic fun <init> (ILnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public fun <init> (Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public fun appendMiraiCodeTo (Ljava/lang/StringBuilder;)V
public final fun component1 ()Lnet/mamoe/mirai/message/data/MusicKind;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
@ -5071,6 +5083,46 @@ public final class net/mamoe/mirai/message/data/RichMessage$Key : net/mamoe/mira
public static synthetic fun share$default (Lnet/mamoe/mirai/message/data/RichMessage$Key;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ServiceMessage;
}
public final class net/mamoe/mirai/message/data/RichMessageKind : java/lang/Enum {
public static final field FORWARD Lnet/mamoe/mirai/message/data/RichMessageKind;
public static final field LONG Lnet/mamoe/mirai/message/data/RichMessageKind;
public static final field MUSIC_SHARE Lnet/mamoe/mirai/message/data/RichMessageKind;
public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/RichMessageKind;
public static fun values ()[Lnet/mamoe/mirai/message/data/RichMessageKind;
}
public final class net/mamoe/mirai/message/data/RichMessageOrigin : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageMetadata {
public static final field Key Lnet/mamoe/mirai/message/data/RichMessageOrigin$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public synthetic fun <init> (ILnet/mamoe/mirai/message/data/RichMessage;Ljava/lang/String;Lnet/mamoe/mirai/message/data/RichMessageKind;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Lnet/mamoe/mirai/message/data/RichMessage;Ljava/lang/String;Lnet/mamoe/mirai/message/data/RichMessageKind;)V
public fun contentToString ()Ljava/lang/String;
public fun equals (Ljava/lang/Object;)Z
public synthetic fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
public fun getKey ()Lnet/mamoe/mirai/message/data/RichMessageOrigin$Key;
public final fun getKind ()Lnet/mamoe/mirai/message/data/RichMessageKind;
public final fun getOrigin ()Lnet/mamoe/mirai/message/data/RichMessage;
public final fun getResourceId ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public static final fun write$Self (Lnet/mamoe/mirai/message/data/RichMessageOrigin;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class net/mamoe/mirai/message/data/RichMessageOrigin$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/RichMessageOrigin$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/RichMessageOrigin;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/RichMessageOrigin;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/RichMessageOrigin$Key : net/mamoe/mirai/message/data/AbstractMessageKey {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public abstract interface class net/mamoe/mirai/message/data/ServiceMessage : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/RichMessage {
public static final field Key Lnet/mamoe/mirai/message/data/ServiceMessage$Key;
public fun appendMiraiCodeTo (Ljava/lang/StringBuilder;)V
@ -5082,6 +5134,7 @@ public final class net/mamoe/mirai/message/data/ServiceMessage$Key : net/mamoe/m
public final class net/mamoe/mirai/message/data/ShowImageFlag : net/mamoe/mirai/message/data/AbstractMessageKey, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageMetadata {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/ShowImageFlag;
public static final field SERIAL_NAME Ljava/lang/String;
public synthetic fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
public fun getKey ()Lnet/mamoe/mirai/message/data/ShowImageFlag;
public fun toString ()Ljava/lang/String;
@ -5339,12 +5392,17 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public static final field Companion Lnet/mamoe/mirai/utils/BotConfiguration$Companion;
public fun <init> ()V
public final fun autoReconnectOnForceOffline ()V
public final synthetic fun contactListCache (Lkotlin/jvm/functions/Function1;)V
public final fun copy ()Lnet/mamoe/mirai/utils/BotConfiguration;
public final fun disableContactCache ()V
public final fun enableContactCache ()V
public final fun fileBasedDeviceInfo ()V
public final fun fileBasedDeviceInfo (Ljava/lang/String;)V
public static synthetic fun fileBasedDeviceInfo$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/lang/String;ILjava/lang/Object;)V
public final fun getAutoReconnectOnForceOffline ()Z
public final fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1;
public final fun getCacheDir ()Ljava/io/File;
public final fun getContactListCache ()Lnet/mamoe/mirai/utils/BotConfiguration$ContactListCache;
public static final fun getDefault ()Lnet/mamoe/mirai/utils/BotConfiguration;
public final fun getDeviceInfo ()Lkotlin/jvm/functions/Function1;
public final fun getFirstReconnectDelayMillis ()J
@ -5360,6 +5418,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public final fun getReconnectionRetryTimes ()I
public final fun getWorkingDir ()Ljava/io/File;
public final synthetic fun inheritCoroutineContext (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun isConvertLineSeparator ()Z
public final fun loadDeviceInfoJson (Ljava/lang/String;)V
public final fun noBotLog ()V
public final fun noNetworkLog ()V
@ -5384,6 +5443,9 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public static synthetic fun redirectNetworkLogToFile$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public final fun setAutoReconnectOnForceOffline (Z)V
public final fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
public final fun setCacheDir (Ljava/io/File;)V
public final fun setContactListCache (Lnet/mamoe/mirai/utils/BotConfiguration$ContactListCache;)V
public final fun setConvertLineSeparator (Z)V
public final fun setDeviceInfo (Lkotlin/jvm/functions/Function1;)V
public final fun setFirstReconnectDelayMillis (J)V
public final fun setHeartbeatPeriodMillis (J)V
@ -5406,6 +5468,18 @@ public final class net/mamoe/mirai/utils/BotConfiguration$Companion {
public abstract interface annotation class net/mamoe/mirai/utils/BotConfiguration$ConfigurationDsl : java/lang/annotation/Annotation {
}
public final class net/mamoe/mirai/utils/BotConfiguration$ContactListCache {
public fun <init> ()V
public final fun getFriendListCacheEnabled ()Z
public final fun getGroupMemberListCacheEnabled ()Z
public final synthetic fun getSaveInterval-UwyO8pc ()D
public final fun getSaveIntervalMillis ()J
public final fun setFriendListCacheEnabled (Z)V
public final fun setGroupMemberListCacheEnabled (Z)V
public final synthetic fun setSaveInterval-LRDsOJo (D)V
public final fun setSaveIntervalMillis (J)V
}
public final class net/mamoe/mirai/utils/BotConfiguration$MiraiProtocol : java/lang/Enum {
public static final field ANDROID_PAD Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol;
public static final field ANDROID_PHONE Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol;

View File

@ -10,7 +10,6 @@
@file:Suppress("UnstableApiUsage", "UNUSED_VARIABLE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@ -35,7 +34,7 @@ buildscript {
plugins {
kotlin("jvm") // version Versions.kotlinCompiler
kotlin("plugin.serialization") version Versions.kotlinCompiler
id("org.jetbrains.dokka") version Versions.dokka
// id("org.jetbrains.dokka") version Versions.dokka
id("net.mamoe.kotlin-jvm-blocking-bridge") version Versions.blockingBridge
id("com.jfrog.bintray") // version Versions.bintray
id("com.gradle.plugin-publish") version "0.12.0" apply false
@ -60,6 +59,8 @@ configure<kotlinx.validation.ApiValidationExtension> {
nonPublicMarkers.add("net.mamoe.mirai.MiraiExperimentalApi")
}
GpgSigner.setup(project)
tasks.register("publishMiraiCoreArtifactsToMavenLocal") {
group = "mirai"
dependsOn(
@ -81,7 +82,6 @@ allprojects {
maven(url = "https://kotlin.bintray.com/kotlinx")
google()
mavenCentral()
maven(url = "https://dl.bintray.com/karlatemp/misc")
}
afterEvaluate {
@ -113,39 +113,63 @@ subprojects {
}
}
fun Project.configureDokka() {
apply(plugin = "org.jetbrains.dokka")
tasks {
val dokkaHtml by getting(DokkaTask::class) {
outputDirectory.set(buildDir.resolve("dokka"))
}
val dokkaGfm by getting(DokkaTask::class) {
outputDirectory.set(buildDir.resolve("dokka-gfm"))
}
}
tasks.withType<DokkaTask>().configureEach {
dokkaSourceSets.configureEach {
perPackageOption {
matchingRegex.set("net\\.mamoe\\.mirai\\.*")
skipDeprecated.set(true)
}
tasks.register("cleanExceptIntellij") {
group = "build"
allprojects.forEach { proj ->
if (proj.name != "mirai-console-intellij") {
for (suppressedPackage in arrayOf(
"""net.mamoe.mirai.internal""",
"""net.mamoe.mirai.internal.message""",
"""net.mamoe.mirai.internal.network""",
"""net.mamoe.mirai.console.internal""",
"""net.mamoe.mirai.console.compiler.common"""
)) {
perPackageOption {
matchingRegex.set(suppressedPackage.replace(".", "\\."))
suppress.set(true)
}
}
// Type mismatch
// proj.tasks.findByName("clean")?.let(::dependsOn)
proj.tasks.findByName("clean")?.let { dependsOn(it) }
}
}
}
extensions.findByName("buildScan")?.withGroovyBuilder {
setProperty("termsOfServiceUrl", "https://gradle.com/terms-of-service")
setProperty("termsOfServiceAgree", "yes")
}
fun Project.useIr() {
kotlinCompilations?.forEach { kotlinCompilation ->
kotlinCompilation.kotlinOptions.freeCompilerArgs += "-Xuse-ir"
}
}
fun Project.configureDokka() {
// apply(plugin = "org.jetbrains.dokka")
// tasks {
// val dokkaHtml by getting(org.jetbrains.dokka.gradle.DokkaTask::class) {
// outputDirectory.set(buildDir.resolve("dokka"))
// }
// val dokkaGfm by getting(org.jetbrains.dokka.gradle.DokkaTask::class) {
// outputDirectory.set(buildDir.resolve("dokka-gfm"))
// }
// }
// tasks.withType<org.jetbrains.dokka.gradle.DokkaTask>().configureEach {
// dokkaSourceSets.configureEach {
// perPackageOption {
// matchingRegex.set("net\\.mamoe\\.mirai\\.*")
// skipDeprecated.set(true)
// }
//
// for (suppressedPackage in arrayOf(
// """net.mamoe.mirai.internal""",
// """net.mamoe.mirai.internal.message""",
// """net.mamoe.mirai.internal.network""",
// """net.mamoe.mirai.console.internal""",
// """net.mamoe.mirai.console.compiler.common"""
// )) {
// perPackageOption {
// matchingRegex.set(suppressedPackage.replace(".", "\\."))
// suppress.set(true)
// }
// }
// }
// }
}
fun Project.configureMppShadow() {
val kotlin =
runCatching {

View File

@ -0,0 +1,108 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
import org.gradle.api.Project
import java.io.File
open class GpgSigner(private val workdir: File) {
private val workdirParent by lazy { workdir.parentFile ?: error("Assertion error: No parent file of $workdir") }
private val workdirName by lazy { workdir.name }
fun verbose(msg: String) {
println("[GPG SIGN] [Verbose] $msg")
}
@Suppress("RemoveExplicitTypeArguments")
private val verbosePrintOnce by lazy<Unit> {
verbose("GPG Signer working dir: $workdir")
verbose("GPG command working dir: $workdirParent")
}
constructor(workdir: String) : this(File(workdir))
object NoopSigner : GpgSigner("build/gpg-noop") {
override fun processGpg(vararg cmds: String) {
}
override fun importKey(file: File) {
}
override fun doSign(file: File) {
}
}
companion object {
private var initialized: Boolean = false
var signer: GpgSigner = NoopSigner
fun setup(project: Project) {
if (initialized) return
initialized = true
val rootProject = project.rootProject
val gpg = rootProject.projectDir.resolve("build-gpg-sign")
gpg.mkdirs()
val keyFile = gpg.resolve("keys.gpg")
val keyFilePub = gpg.resolve("keys.gpg.pub")
if (keyFile.isFile) {
val homedir = gpg.resolve("homedir")
signer = GpgSigner(homedir.absolutePath)
if (!homedir.resolve("pubring.kbx").isFile) {
signer.importKey(keyFile)
if (keyFilePub.isFile) {
signer.importKey(keyFilePub)
} else {
println("[GPG SIGN] Missing public key storage")
println("[GPG SIGN] GPG Sign 2nd verity may failed.")
}
}
} else {
println("[GPG SIGN] GPG Key not found.")
println("[GPG SIGN] GPG Signer will not setup")
println("[GPG SIGN] Key file location: $keyFile")
}
}
}
open fun processGpg(
vararg cmds: String
) {
workdir.mkdirs()
verbosePrintOnce
val response = ProcessBuilder().command(ArrayList<String>().apply {
add("gpg")
add("--homedir"); add(workdirName)
addAll(cmds)
}.also {
verbose("Processing " + it.joinToString(" "))
}).directory(workdirParent)
.inheritIO()
.start()
.waitFor()
if (response != 0) {
error("Exit Response $response")
}
}
open fun importKey(file: File) {
processGpg("--batch", "--import", file.toString())
}
open fun doSign(file: File) {
if (!file.isFile) {
println("[GPG SIGN] $file not a file")
return
}
println("[GPG SIGN] Signing $file")
File("${file.path}.asc").delete()
processGpg("-a", "--batch", "--no-tty", "--detach-sig", "--sign", file.toString())
processGpg("--verify", "$file.asc", file.toString())
}
}

View File

@ -13,6 +13,7 @@
)
import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin
import keys.SecretKeys
import org.gradle.api.Project
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.bundling.Jar
@ -21,17 +22,9 @@ import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.registering
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
fun Project.configureBintray() {
fun Project.configureRemoteRepos(
bintrayPkgName: String = "mirai-core"
) {
tasks.register("ensureBintrayAvailable") {
doLast {
if (!project.isBintrayAvailable()) {
@ -39,19 +32,53 @@ fun Project.configureBintray() {
}
}
}
tasks.register("ensureMavenCentralAvailable") {
doLast {
if (GpgSigner.signer == GpgSigner.NoopSigner) {
error("GPG Signer isn't available.")
}
val keys = SecretKeys.getCache(project)
if (!keys.loadKey("sonatype").isValid) {
error("Maven Central isn't available.")
}
}
}
if (isBintrayAvailable()) {
publishing {
repositories {
publishing {
// sonatype
val keys = SecretKeys.getCache(project)
repositories {
val sonatype = keys.loadKey("sonatype")
if (sonatype.isValid) {
maven {
setUrl("https://api.bintray.com/maven/him188moe/mirai/mirai-core/;publish=1;override=1")
name = "MavenCentral"
// Maven Central
setUrl("https://oss.sonatype.org/service/local/staging/deploy/maven2")
credentials {
username = sonatype.user
password = sonatype.password
}
}
} else {
println("SonaType is not available")
}
if (isBintrayAvailable()) {
maven {
name = "Bintray"
setUrl("https://api.bintray.com/maven/him188moe/mirai/$bintrayPkgName/;publish=1;override=1")
credentials {
username = Bintray.getUser(project)
password = Bintray.getKey(project)
}
}
} else {
println("bintray isn't available.")
}
}
}
}
@ -63,30 +90,29 @@ inline fun Project.configurePublishing(
bintrayPkgName: String = artifactId,
vcs: String = "https://github.com/mamoe/mirai"
) {
configureBintray()
configureRemoteRepos(
bintrayPkgName = bintrayPkgName
)
apply<ShadowPlugin>()
if (!project.isBintrayAvailable()) {
println("bintray isn't available. NO PUBLICATIONS WILL BE SET")
return
}
if (project.isBintrayAvailable()) {
bintray {
user = Bintray.getUser(project)
key = Bintray.getKey(project)
bintray {
user = Bintray.getUser(project)
key = Bintray.getKey(project)
setPublications("mavenJava")
setConfigurations("archives")
setPublications("mavenJava")
setConfigurations("archives")
publish = true
override = true
publish = true
override = true
pkg.apply {
repo = bintrayRepo
name = bintrayPkgName
setLicenses("AGPLv3")
publicDownloadNumbers = true
vcsUrl = vcs
pkg.apply {
repo = bintrayRepo
name = bintrayPkgName
setLicenses("AGPLv3")
publicDownloadNumbers = true
vcsUrl = vcs
}
}
}
@ -94,6 +120,10 @@ inline fun Project.configurePublishing(
archiveClassifier.set("sources")
from(sourceSets["main"].allSource)
}
val stubJavadoc = tasks.register("javadocJar", Jar::class) {
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
archiveClassifier.set("javadoc")
}
publishing {
publications {
@ -104,16 +134,15 @@ inline fun Project.configurePublishing(
setArtifactId(artifactId)
version = project.version.toString()
pom.withXml {
val root = asNode()
root.appendNode("description", description)
root.appendNode("name", project.name)
root.appendNode("url", vcs)
root.children().last()
}
setupPom(
project = project,
vcs = vcs
)
artifact(sourcesJar.get())
artifact(stubJavadoc.get())
}
}
configGpgSign(this@configurePublishing)
}
}

View File

@ -21,7 +21,7 @@ fun logPublishing(message: String) {
}
fun Project.configureMppPublishing() {
configureBintray()
configureRemoteRepos()
// mirai does some magic on MPP targets
afterEvaluate {
@ -42,10 +42,13 @@ fun Project.configureMppPublishing() {
.forEach { publication ->
val moduleFile = buildDir.resolve("publications/${publication.name}/module.json")
if (moduleFile.exists()) {
publication.artifact(object :
val artifact = (object :
org.gradle.api.publish.maven.internal.artifact.FileBasedMavenArtifact(moduleFile) {
override fun getDefaultExtension() = "module"
})
publication.artifact(artifact)
GpgSigner.signer.doSign(moduleFile)
publication.artifact(GPGSignMavenArtifact(artifact))
}
}
}
@ -61,9 +64,10 @@ fun Project.configureMppPublishing() {
logPublishing("Publications: ${publications.joinToString { it.name }}")
publications.filterIsInstance<MavenPublication>().forEach { publication ->
if (publication.name != "kotlinMultiplatform") {
publication.artifact(stubJavadoc)
}
// Maven Central always require javadoc.jar
publication.artifact(stubJavadoc)
publication.setupPom(project)
logPublishing(publication.name)
when (val type = publication.name) {
@ -86,6 +90,7 @@ fun Project.configureMppPublishing() {
}
}
}
configGpgSign(this@configureMppPublishing)
}
}
}
@ -129,6 +134,6 @@ val publishPlatformArtifactsInRootModule: Project.(MavenPublication) -> Unit = {
}
}
private fun MavenArtifact.smartToString(): String {
public fun MavenArtifact.smartToString(): String {
return "${file.path}, classifier=${classifier}, ext=${extension}"
}

View File

@ -0,0 +1,102 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
import org.gradle.api.Project
import org.gradle.api.internal.tasks.DefaultTaskDependency
import org.gradle.api.internal.tasks.TaskDependencyInternal
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenArtifact
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.internal.artifact.AbstractMavenArtifact
import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication
import java.io.File
open class GPGSignMavenArtifact(
private val delegate: MavenArtifact,
private val tasks: TaskDependencyInternal = TaskDependencyInternal.EMPTY
) : AbstractMavenArtifact() {
override fun getFile(): File {
return File(delegate.file.path + ".asc")
}
override fun shouldBePublished(): Boolean = (delegate as? AbstractMavenArtifact)?.shouldBePublished() ?: true
override fun getDefaultExtension(): String = delegate.extension + ".asc"
override fun getDefaultClassifier(): String = delegate.classifier ?: ""
override fun getDefaultBuildDependencies(): TaskDependencyInternal = tasks
}
class NameCounter(val name: String) {
var counter = 0
val nextName: String
get() = name + if (counter == 0) {
counter = 1; ""
} else {
counter++; counter
}
}
object PublishingAccess {
fun getMetadataArtifacts(publication: MavenPublication): Collection<MavenArtifact> {
if (publication is DefaultMavenPublication) {
return DefaultMavenPublication::class.java.getDeclaredField("metadataArtifacts")
.also { it.isAccessible = true }
.get(publication) as Collection<MavenArtifact>
}
return emptyList()
}
}
fun PublishingExtension.configGpgSign(project: Project) {
if (GpgSigner.signer === GpgSigner.NoopSigner) {
return
}
val tasks = DefaultTaskDependency()
val signArtifactsGPG = NameCounter("signArtifactsGPG")
publications.forEach { publication ->
if (publication is MavenPublication) {
val artifacts0: Collection<Pair<Collection<MavenArtifact>, (MavenArtifact) -> Unit>> = listOf(
publication.artifacts to { publication.artifact(it) }, // main artifacts
PublishingAccess.getMetadataArtifacts(publication).let { artifacts -> // pom files
if (artifacts is MutableCollection<MavenArtifact>) {
artifacts to { artifacts.add(it) }
} else {
artifacts to { publication.artifact(it) }
}
}
)
val allArtifacts = artifacts0.flatMap { it.first }.toList()
if (allArtifacts.isNotEmpty()) {
tasks.add(project.tasks.create(signArtifactsGPG.nextName) {
group = "publishing"
doLast {
allArtifacts.forEach { artifact ->
if ((artifact as? AbstractMavenArtifact)?.shouldBePublished() != false) {
GpgSigner.signer.doSign(artifact.file)
}
}
}
allArtifacts.forEach {
dependsOn(it.buildDependencies)
}
})
artifacts0.forEach { (artifacts, artifactsRegister) ->
artifacts.toList().forEach { artifact ->
logPublishing("gpg sign for artifact ${artifact.smartToString()}")
artifactsRegister(GPGSignMavenArtifact(artifact, tasks))
}
}
}
}
}
}

View File

@ -15,6 +15,7 @@
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.publish.PublicationContainer
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.ExistingDomainObjectDelegate
import org.gradle.kotlin.dsl.RegisteringDomainObjectDelegateProviderWithTypeAndAction
@ -77,3 +78,40 @@ val Project.publications: PublicationContainer
}
return ret
}
fun MavenPublication.setupPom(
project: Project,
vcs: String = "https://github.com/mamoe/mirai"
) {
pom {
scm {
url.set(vcs)
connection.set("scm:$vcs.git")
developerConnection.set("scm:${vcs.replace("https:", "git:")}.git")
}
licenses {
license {
name.set("GNU AGPLv3")
url.set("https://github.com/mamoe/mirai/blob/master/LICENSE")
}
}
developers {
developer {
id.set("mamoe")
name.set("Mamoe Technologies")
email.set("support@mamoe.net")
}
}
}
pom.withXml {
val root = asNode()
root.appendNode("description", project.description)
root.appendNode("name", project.name)
root.appendNode("url", vcs)
}
}

View File

@ -12,7 +12,7 @@
import org.gradle.api.attributes.Attribute
object Versions {
const val project = "2.2.2"
const val project = "2.5.0-dev-2"
const val core = project
const val console = project
@ -32,7 +32,7 @@ object Versions {
const val io = "0.1.16"
const val coroutinesIo = "0.1.16"
const val blockingBridge = "1.7.4"
const val blockingBridge = "1.10.0"
const val androidGradlePlugin = "4.1.1"
const val android = "4.1.1.4"
@ -104,6 +104,6 @@ const val yamlkt = "net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}"
const val `jetbrains-annotations` = "org.jetbrains:annotations:19.0.0"
const val `caller-finder` = "io.github.karlatemp:caller:1.0.1"
const val `caller-finder` = "io.github.karlatemp:caller:1.1.1"
const val `android-runtime` = "com.google.android:android:${Versions.android}"

View File

@ -0,0 +1,123 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package keys
import org.gradle.api.Project
import java.io.BufferedReader
open class SecretKeys(
val type: String,
val user: String,
val password: String
) {
class Invalid(
type: String,
override val isDisabled: Boolean = false
) : SecretKeys(type, "", "") {
override val isValid: Boolean get() = false
override fun requireNotInvalid(): Nothing {
error(
"""
Key $type not found.
Please lease specify by creating a file $type.key in projectDir/build-secret-keys
or by providing JVM parameter '$type.user', `$type.password`
""".trimIndent()
)
}
}
companion object {
val keyCaches = mutableMapOf<Project, ProjectKeysCache>()
@JvmStatic
fun getCache(project: Project): ProjectKeysCache =
keyCaches.computeIfAbsent(project, SecretKeys::ProjectKeysCache)
}
class ProjectKeysCache(val project: Project) {
val keys = mutableMapOf<String, SecretKeys>()
fun loadKey(type: String) = keys.computeIfAbsent(type, this::loadKey0)
private fun loadKey0(type: String): SecretKeys {
project.parent?.let { parent ->
getCache(parent).loadKey(type).takeIf {
it.isValid || it.isDisabled
}?.let { return it }
}
val secretKeys = project.projectDir.resolve("build-secret-keys")
kotlin.run {
val secretKeyFile = secretKeys.resolve("$type.disable").takeIf { it.isFile }
?: secretKeys.resolve("$type.disable.txt")
if (secretKeyFile.isFile) return Invalid(type, true) // Disabled
}
// Load from secretKeys/$type.key
kotlin.run {
val secretKeyFile = secretKeys.resolve("$type.key").takeIf { it.isFile }
?: secretKeys.resolve("$type.key.txt")
if (secretKeyFile.isFile) {
secretKeyFile.bufferedReader().use {
fun BufferedReader.readLineNonEmpty(): String {
while (true) {
val nextLine = readLine() ?: return ""
if (nextLine.isNotBlank()) {
return nextLine.trim()
}
}
}
return SecretKeys(type, it.readLineNonEmpty(), it.readLineNonEmpty())
}
}
}
// Load from project/%type.key, user
kotlin.run {
val userFile = project.projectDir.resolve("$type.user.txt")
val keyFile = project.projectDir.resolve("$type.key.txt")
if (userFile.isFile && keyFile.isFile) {
return SecretKeys(type, userFile.readText().trim(), keyFile.readText().trim())
}
}
// Load from property $type.user, $type.password
fun findProperty(type: String): String? {
val p = project.findProperty(type)
?: System.getProperty(type)
?: System.getenv(type)
return p?.toString()
}
val tUser = findProperty("$type.user")
?: findProperty("${type}_user")
val tPassword = findProperty("$type.password")
?: findProperty("$type.passwd")
?: findProperty("$type.key")
?: findProperty("${type}_password")
?: findProperty("${type}_passwd")
?: findProperty("${type}_key")
if (tUser != null && tPassword != null) {
return SecretKeys(type, tUser, tPassword)
}
return Invalid(type)
}
}
open val isValid: Boolean get() = true
open val isDisabled: Boolean get() = false
open fun requireNotInvalid(): SecretKeys = this
}

View File

@ -7,18 +7,9 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
import keys.SecretKeys
import org.gradle.api.Project
import org.gradle.kotlin.dsl.provideDelegate
import java.io.File
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
fun Project.isBintrayAvailable() = Bintray.isBintrayAvailable(project)
@Suppress("DuplicatedCode")
@ -34,76 +25,18 @@ object Bintray {
@JvmStatic
fun getUser(project: Project): String {
kotlin.runCatching {
@Suppress("UNUSED_VARIABLE", "LocalVariableName")
val bintray_user: String by project
return bintray_user
}
kotlin.runCatching {
@Suppress("UNUSED_VARIABLE", "LocalVariableName")
val bintray_user: String by project.rootProject
return bintray_user
}
System.getProperty("bintray_user", null)?.let {
return it.trim()
}
File(File(System.getProperty("user.dir")).parent, "/bintray.user.txt").let { local ->
if (local.exists()) {
return local.readText().trim()
}
}
File(File(System.getProperty("user.dir")), "/bintray.user.txt").let { local ->
if (local.exists()) {
return local.readText().trim()
}
}
error(
"Cannot find bintray user, " +
"please specify by creating a file bintray.user.txt in project dir, " +
"or by providing JVM parameter 'bintray_user'"
)
return SecretKeys.getCache(project)
.loadKey("bintray")
.requireNotInvalid()
.user
}
@JvmStatic
fun getKey(project: Project): String {
kotlin.runCatching {
@Suppress("UNUSED_VARIABLE", "LocalVariableName")
val bintray_key: String by project
return bintray_key
}
kotlin.runCatching {
@Suppress("UNUSED_VARIABLE", "LocalVariableName")
val bintray_key: String by project.rootProject
return bintray_key
}
System.getProperty("bintray_key", null)?.let {
return it.trim()
}
File(File(System.getProperty("user.dir")).parent, "/bintray.key.txt").let { local ->
if (local.exists()) {
return local.readText().trim()
}
}
File(File(System.getProperty("user.dir")), "/bintray.key.txt").let { local ->
if (local.exists()) {
return local.readText().trim()
}
}
error(
"Cannot find bintray key, " +
"please specify by creating a file bintray.key.txt in project dir, " +
"or by providing JVM parameter 'bintray_key'"
)
return SecretKeys.getCache(project)
.loadKey("bintray")
.requireNotInvalid()
.password
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
import keys.SecretKeys
plugins {
id("io.codearte.nexus-staging") version "0.22.0"
}
description = "Mirai CI Methods for Releasing"
nexusStaging {
packageGroup = rootProject.group.toString()
val keys = SecretKeys.getCache(project).loadKey("sonatype")
username = keys.user
password = keys.password
}

View File

@ -68,6 +68,25 @@ workingDir = File("C:/mirai")
setWorkingDir(File("C:/mirai"))
```
#### 修改缓存目录
缓存目录会相对于 `workingDir` 解析。如 `File("cache")` 将会解析为 `workingDir` 内的 `cache` 目录。而 `File("C:/cache")` 将会解析为绝对的 `C:/cache` 目录。
默认为 `File("cache")`
要修改缓存目录(自 mirai 2.4.0
```
// Kotlin
cacheDir = File("cache") // 最终为 workingDir 目录中的 cache 目录
cacheDir = File("C:/cache") // 最终为 C:/cache
// Java
setCacheDir(File("cache")) // 最终为 workingDir 目录中的 cache 目录
setCacheDir(File("C:/cache")) // 最终为 C:/cache
```
目前缓存目录会存储列表缓存、登录服务器、资源会话秘钥等。这些数据的存储方式有可能变化,请不要修改缓存目录中的文件。
#### 设备信息
Bot 默认使用全随机的设备信息。**在更换账号地点时候使用随机设备信息可能会导致无法登录**,当然,**成功登录时使用的设备信息也可以保存后在新的设备使用**。
@ -102,7 +121,6 @@ protocol = BotConfiguration.MiraiProtocol.ANDROID_PAD
// Java
setProtocol(MiraiProtocol.ANDROID_PAD)
```
#### 重定向日志
Bot 有两个日志类别,`Bot` 或 `Net`。`Bot` 为通常日志,如收到事件。`Net` 为网络日志,包含收到和发出的每一个包和网络层解析时遇到的错误。
@ -149,6 +167,34 @@ setLoginSolver(new YourLoginSolver())
> 要获取更多有关 `LoginSolver` 的信息,查看 [LoginSolver.kt](../mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt#L32)
#### 启用列表缓存
Mirai 在启动时会拉取全部好友列表和群成员列表。当账号拥有过多群时登录可能缓慢,开启列表缓存会大幅加速登录过程。
Mirai 自动根据事件更新列表,并在每次登录时与服务器校验缓存有效性,**但有时候可能发生意外情况导致列表没有同步。如果出现找不到群员或好友等不同步情况,请关闭缓存并[提交 Bug](https://github.com/mamoe/mirai/issues/new?assignees=&labels=question&template=bug.md)**
要开启列表缓存(自 mirai 2.4.0
```
// 开启所有列表缓存
enableContactCache()
```
也可以只开启部分缓存:
```
// Kotlin
contactListCache {
friendListCacheEnabled = true // 开启好友列表缓存
groupMemberListCacheEnabled = true // 开启群成员列表缓存
saveIntervalMillis = 60_000 // 可选设置有更新时的保存时间间隔, 默认 60 秒
}
// Java
contactListCache.setFriendListCacheEnabled(true) // 开启好友列表缓存
contactListCache.setGroupMemberListCacheEnabled(true) // 开启群成员列表缓存
contactListCache.setSaveIntervalMillis(60000) // 可选设置有更新时的保存时间间隔, 默认 60 秒
```
### 获取当前所有 `Bot` 实例
在登录后 `Bot` 实例会被自动记录。可在 `Bot.instances` 获取到当前**在线**的所有 `Bot` 列表。
@ -168,11 +214,14 @@ setLoginSolver(new YourLoginSolver())
### 常见登录失败原因
[#993]: https://github.com/mamoe/mirai/discussions/993
| 错误信息 | 可能的原因 | 可能的解决方案 |
|:--------------|:---------------|:----------------------|
| 当前版本过低 | 密码错误 | 检查密码 |
| 当前上网环境异常 | 设备锁 | 开启或关闭设备锁后重试登录 |
| 当前版本过低 | 密码错误 | 检查密码或修改密码到 16 位以内 |
| 当前上网环境异常 | 设备锁 | 开启或关闭设备锁 (登录保护) |
| 禁止登录 | 需要处理滑块验证码 | [project-mirai/mirai-login-solver-selenium] |
| 密码错误 | 密码错误或过长 | 手机协议最大支持 16 位密码 ([#993]). 在官方 PC 客户端登录后修改密码 |
若以上方案无法解决问题,请尝试 [切换登录协议](#切换登录协议) 和 **[处理滑动验证码](#处理滑动验证码)**。

View File

@ -11,8 +11,8 @@
| 版本类型 | 版本号 |
|:------:|:------------------------------:|
| 稳定 | 2.2.2 |
| 预览 | - |
| 稳定 | 2.3.2 |
| 预览 | 2.4-RC |
| 开发 | [![Version]][Bintray Download] |
### 配置项目
@ -40,7 +40,7 @@ repositories {
}
dependencies {
api("net.mamoe", "mirai-core", "2.2.2") // 替换为你需要的版本号
api("net.mamoe", "mirai-core", "2.3.2") // 替换为你需要的版本号
}
```
@ -64,7 +64,7 @@ repositories {
}
dependencies {
api('net.mamoe', 'mirai-core', '2.2.2') // 替换为你需要的版本号
api('net.mamoe', 'mirai-core', '2.3.2') // 替换为你需要的版本号
}
```
@ -77,7 +77,7 @@ dependencies {
mirai 在开发时需要 `net.mamoe:mirai-core-api`, 在运行时需要 `net.mamoe:mirai-core`。可以在开发和编译时只依赖 `mirai-core-api`,会减轻对 IDE 的负担。
```kotlin
dependencies {
val miraiVersion = "2.2.2" // 替换为你需要的版本号
val miraiVersion = "2.3.2" // 替换为你需要的版本号
api("net.mamoe", "mirai-core-api", miraiVersion) // 编译代码使用
runtimeOnly("net.mamoe", "mirai-core", miraiVersion) // 运行时使用
}
@ -105,7 +105,7 @@ dependencies {
<dependency>
<groupId>net.mamoe</groupId>
<artifactId>mirai-core-jvm</artifactId>
<version>2.2.2</version> <!-- 替换版本为你需要的版本 -->
<version>2.3.2</version> <!-- 替换版本为你需要的版本 -->
</dependency>
</dependencies>
```

View File

@ -63,6 +63,9 @@ Mirai 支持富文本消息。
各类型消息元素及其 `contentToString()` 如下表格所示。
[`MessageContent`]: ../mirai-core-api/src/commonMain/kotlin/message/data/SingleMessage.kt
[`MessageMetadata`]: ../mirai-core-api/src/commonMain/kotlin/message/data/SingleMessage.kt
[`PlainText`]: ../mirai-core-api/src/commonMain/kotlin/message/data/PlainText.kt
[`At`]: ../mirai-core-api/src/commonMain/kotlin/message/data/At.kt
[`AtAll`]: ../mirai-core-api/src/commonMain/kotlin/message/data/AtAll.kt
@ -73,6 +76,7 @@ Mirai 支持富文本消息。
[`FlashImage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt
[`MarketFace`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt
[`MusicShare`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt
[`Dice`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt
[`MessageSource`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt
[`QuoteReply`]: ../mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt
@ -80,34 +84,49 @@ Mirai 支持富文本消息。
[`SimpleServiceMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt
[`Voice`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt
[`ForwardMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt
[`ShowImageFlag`]: ../mirai-core-api/src/commonMain/kotlin/message/data/ShowImageFlag.kt
[`RichMessageOrigin`]: ../mirai-core-api/src/commonMain/kotlin/message/data/RichMessageOrigin.kt
| [`MessageContent`] 类型 | 解释 | `contentToString()` | 最低支持的版本 |
|:------------------------:|:--------------------|:------------------------|:---------------------:|
| [`PlainText`] | 纯文本 | `$content` | 2.0 |
| [`Image`] | 自定义图片 | `[图片]` | 2.0 |
| [`At`] | 提及某人 | `@$target` | 2.0 |
| [`AtAll`] | 提及全体成员 | `@全体成员` | 2.0 |
| [`Face`] | 原生表情 | `[表情对应的中文名]` | 2.0 |
| [`FlashImage`] | 闪照 | `[闪照]` | 2.0 |
| [`PokeMessage`] | 戳一戳消息(消息非动作) | `[戳一戳]` | 2.0 |
| [`VipFace`] | VIP 表情 | `[${kind.name}]x$count` | 2.0 |
| [`LightApp`] | 小程序 | `$content` | 2.0 |
| [`Voice`] | 语音 | `[语音消息]` | 2.0 |
| [`MarketFace`] | 商城表情 | `[表情对应的中文名]` | 2.0 |
| [`ForwardMessage`] | 合并转发 | `[转发消息]` | 2.0 *<sup>(1)</sup>* |
| [`SimpleServiceMessage`] | (不稳定)服务消息 | `$content` | 2.0 |
| [`MusicShare`] | 音乐分享 | `[分享]曲名` | 2.1 |
| [`Dice`] | 骰子 | `[骰子:$value]` | 2.5 |
| [`MessageMetadata`] 类型 | 解释 | 最低支持的版本 |
|:-----------------------:|:------------|:------------:|
| [`MessageSource`] | 消息来源元数据 | 2.0 |
| [`QuoteReply`] | 引用回复 | 2.0 |
| [`ShowImageFlag`] | 秀图标识 | 2.2 |
| [`RichMessageOrigin`] | 富文本消息源 | 2.3 |
| 消息类型 | 属性 | 解释 | `contentToString()` |
|:------------------------:|:--------------------------------------------|:--------------------|:------------------------|
| [`PlainText`] | `content: String` | 纯文本 | `$content` |
| [`Image`] | `imageId: String` | 自定义图片 | `[图片]` |
| [`At`] | `target: Int` | 提及某人 | `@$target` |
| [`AtAll`] | | 提及全体成员 | `@全体成员` |
| [`Face`] | `id: Int` | 原生表情 | `[表情对应的中文名]` |
| [`FlashImage`] | `image: Image` | 闪照 | `[闪照]` |
| [`PokeMessage`] | `name: String`, `pokeType: Int` , `id: Int` | 戳一戳消息(消息非动作) | `[戳一戳]` |
| [`VipFace`] | `kind: VipFace.Kind`, `count: Int` | VIP 表情 | `[${kind.name}]x$count` |
| [`LightApp`] | `content: String` | 小程序 | `$content` |
| [`Voice`] | `content: String` | 语音 | `$content` |
| [`MarketFace`] | `id: Int, name: String` | 商城表情 | `[表情对应的中文名]` |
| [`MessageSource`] | ... | 消息来源元数据 | *空字符串* |
| [`QuoteReply`] | `source: MessageSource` | 引用回复 | *空字符串* |
| [`ForwardMessage`] | ... | 合并转发 | `[转发消息]` |
| [`SimpleServiceMessage`] | `serviceId: Int, content: String` | (不稳定)服务消息 | `$content` |
| [`MusicShare`] | ... | (自 2.1) 音乐分享 | `[分享]曲名` |
**请打开相关消息类型的源码查看用法。**
> *(1)*: [`ForwardMessage`] 在 2.0 支持发送, 在 2.3 支持接收
> 回到 [目录](#目录)
## 消息链
[`MessageChain`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt
[`SingleMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Message.kt
[`SingleMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/SingleMessage.kt
[`CodableMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt
前文已经介绍消息链,这里简略介绍消息链的使用。详细的使用请查看源码内注释。
@ -203,10 +222,18 @@ MessageChain chain = new MessageChainBuilder()
.build();
```
### 作为字符串处理消息
通常要把消息作为字符串处理,在 Kotlin 使用 `message.content` 或在 Java 使用 `message.contentToString()`
获取到的字符串表示只包含各 [`MessageContent`] 以官方风格显示的消息内容。如 `"你本次测试的成绩是[图片]"`、`[语音]`、`[微笑]`
### 元素唯一性
[`MessageKey`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageKey.kt
[`ConstrainSingle`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Message.kt#L273-L287
[`ConstrainSingle`]: ../mirai-core-api/src/commonMain/kotlin/message/data/ConstrainSingle.kt
[`HummerMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt
部分元素只能单一存在于消息链中。这样的元素实现接口 [`ConstrainSingle`]。
@ -332,6 +359,7 @@ at.serializeToMiraiCode() // 结果为 `[mirai:at:123]`
| [`VipFace`] | `[mirai:vipface:${kind.id},${kind.name},$count]` |
| [`LightApp`] | `[mirai:app:$content]` |
| [`SimpleServiceMessage`] | `[mirai:service:$serviceId,$content]` |
| [`Dice`] | `[mirai:dice:$value]` |
### 由 mirai 码字符串取得 `MessageChain` 实例

View File

@ -19,9 +19,9 @@
[iTXTech/mirai-js]: https://github.com/iTXTech/mirai-js
[iTXTech/mirai-kts]: https://github.com/iTXTech/mirai-kts
[GraiaProject/Application]: https://github.com/GraiaProject/Application
[NoneBot]: https://github.com/nonebot/nonebot2
[RedBeanN/node-mirai]: https://github.com/RedBeanN/node-mirai
[Logiase/gomirai]: https://github.com/Logiase/gomirai
[StageGuard/mirai-rhinojs-sdk]: https://github.com/StageGuard/mirai-rhinojs-sdk
[cyanray/mirai-cpp]: https://github.com/cyanray/mirai-cpp
[Chlorie/miraipp]: https://github.com/Chlorie/miraipp-template
[Executor-Cheng/mirai-CSharp]: https://github.com/Executor-Cheng/mirai-CSharp
@ -31,30 +31,33 @@
[theGravityLab/ProjHyperai]: https://github.com/theGravityLab/ProjHyperai
[yyuueexxiinngg/onebot-kotlin]: https://github.com/yyuueexxiinngg/onebot-kotlin
[Nambers/MiraiCP]:https://github.com/Nambers/MiraiCP
[drinkal/Mirai-js]:https://github.com/drinkal/Mirai-js
[Rhino]: https://github.com/mozilla/rhino
[OneBot]: https://github.com/howmanybots/onebot
| 技术 | 实现 | 维护者及项目地址 |
|:-------------------|:--------------------------|:--------------------------------------------|
| ***Mirai Http*** | Mirai 标准 | [mamoe/mirai-api-http] |
| *OneBot Http* | [OneBot] 标准 | [yyuueexxiinngg/onebot-kotlin] |
| `C++` | JNI | [Nambers/MiraiCP] |
| `Kotlin Scripting` | JVM | [iTXTech/mirai-kts] |
| `Python` | *Mirai Http* | [Graia Framework][GraiaProject/Application] |
| `C++` | *Mirai Http* | [cyanray/mirai-cpp] |
| `C++` | *Mirai Http* | [Chlorie/miraipp] |
| `C#` | *Mirai Http* | [Executor-Cheng/mirai-CSharp] |
| `Rust` | *Mirai Http* | [HoshinoTented/mirai-rs] |
| `JavaScript` | [Rhino] / JVM | [iTXTech/mirai-js] |
| `JavaScript` | Node.js / *Mirai Http* | [RedBeanN/node-mirai] |
| `JavaScript` | TypeScript / *Mirai Http* | [YunYouJun/mirai-ts] |
| `JavaScript` | [Rhino] / JVM | [StageGuard/mirai-rhinojs-sdk] |
| `.Net/C#` | *Mirai Http* | [Hyperai][theGravityLab/ProjHyperai] |
| `Go` | *Mirai Http* | [Logiase/gomirai] |
| `易语言` | *Mirai Http* | [only52607/e-mirai] |
| *酷 Q DLL 插件* | JNI | [iTXTech/mirai-native] |
| 技术 | 实现 | 维护者及项目地址 |
|:-------------------|:-----------------------------|:--------------------------------------------|
| ***Mirai Http*** | Mirai 标准 | [mamoe/mirai-api-http] |
| *OneBot Http* | [OneBot] 标准 | [yyuueexxiinngg/onebot-kotlin] |
| `Kotlin Scripting` | JVM | [iTXTech/mirai-kts] |
| `Python` | *Mirai Http* | [Graia Framework][GraiaProject/Application] |
| `Python` | *Mirai Http* / *OneBot Http* | [NoneBot] |
| `C++` | JNI | [Nambers/MiraiCP] |
| `C++` | *Mirai Http* | [cyanray/mirai-cpp] |
| `C++` | *Mirai Http* | [Chlorie/miraipp] |
| `C#` | *Mirai Http* | [Executor-Cheng/mirai-CSharp] |
| `C#` | *Mirai Http* | [Hyperai][theGravityLab/ProjHyperai] |
| `Rust` | *Mirai Http* | [HoshinoTented/mirai-rs] |
| `JavaScript` | [Rhino] / JVM | [iTXTech/mirai-js] |
| `JavaScript` | Node.js / *Mirai Http* | [RedBeanN/node-mirai] |
| `JavaScript` | TypeScript / *Mirai Http* | [YunYouJun/mirai-ts] |
| `JavaScript` | Node.js / *Mirai Http* | [drinkal/Mirai-js] |
| `Go` | *Mirai Http* | [Logiase/gomirai] |
| `易语言` | *Mirai Http* | [only52607/e-mirai] |
| *酷 Q DLL 插件* | JNI | [iTXTech/mirai-native] |
> 排名不分先后
> *想在这里添加你的项目?欢迎[提交 PR](https://github.com/mamoe/mirai/edit/dev/docs/README.md)。*
特别地,有一些 SDK 直接基于 mirai-core 开发,不需要 [`mirai-console`]

View File

@ -1,11 +1,12 @@
#
# Copyright 2019-2020 Mamoe Technologies and contributors.
# Copyright 2019-2021 Mamoe Technologies and contributors.
#
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
#
# https://github.com/mamoe/mirai/blob/master/LICENSE
#
# style guide
kotlin.code.style=official
# config
@ -17,4 +18,5 @@ org.gradle.vfs.watch=true
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false
#kotlin.mpp.enableGranularSourceSetsMetadata=true
systemProp.org.gradle.internal.publish.checksums.insecure=true
systemProp.org.gradle.internal.publish.checksums.insecure=true
gnsp.disableApplyOnlyOnRootProjectEnforcement=true

0
gradlew vendored Normal file → Executable file
View File

0
gradlew.bat vendored Normal file → Executable file
View File

@ -1 +1 @@
Subproject commit 3f98d8ec2abfa963c5f720f32b7b27e863569bc8
Subproject commit 086604ce67792ae835b3cf34e1b41edf11d0e7d6

View File

@ -61,6 +61,7 @@ kotlin {
api(`kotlinx-serialization-json`)
implementation(`kotlinx-serialization-protobuf`)
api(`kotlinx-coroutines-core`)
implementation(`jetbrains-annotations`)
// api(`kotlinx-coroutines-jdk8`)
api(`ktor-client-okhttp`)

View File

@ -43,6 +43,7 @@ public suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
*
* @see BotFactory 构造 [Bot] 的工厂, [Bot] 唯一的构造方式.
*/
@JvmBlockingBridge
public interface Bot : CoroutineScope, ContactOrBot, UserOrBot {
/**
* Bot 配置
@ -161,7 +162,6 @@ public interface Bot : CoroutineScope, ContactOrBot, UserOrBot {
* @throws LoginFailedException 正常登录失败时抛出
* @see alsoLogin `.apply { login() }` 捷径
*/
@JvmBlockingBridge
public suspend fun login()
/**
@ -232,7 +232,6 @@ public interface Bot : CoroutineScope, ContactOrBot, UserOrBot {
* 挂起协程直到 [Bot] 协程被关闭 ([Bot.close]).
* 即使 [Bot] 离线, 也会等待直到协程关闭.
*/
@JvmBlockingBridge
public suspend fun join(): Unit = supervisorJob.join()
@ -243,7 +242,6 @@ public interface Bot : CoroutineScope, ContactOrBot, UserOrBot {
*
* @param cause 原因. null 时视为正常关闭, null 时视为异常关闭
*/
@JvmBlockingBridge
public suspend fun closeAndJoin(cause: Throwable? = null) {
close(cause)
join()

View File

@ -41,6 +41,7 @@ public val Mirai: IMirai by lazy { findMiraiInstance() }
*
* @see Mirai 获取实例
*/
@JvmBlockingBridge
public interface IMirai : LowLevelApiAccessor {
/**
* 请优先使用 [BotFactory.INSTANCE]
@ -121,13 +122,11 @@ public interface IMirai : LowLevelApiAccessor {
* @see IMirai.recallMessage (扩展函数) 接受参数 [MessageChain]
* @see MessageSource.recall 撤回消息扩展
*/
@JvmBlockingBridge
public suspend fun recallMessage(bot: Bot, source: MessageSource)
/**
* 发送戳一戳消息
*/
@JvmBlockingBridge
public suspend fun sendNudge(bot: Bot, nudge: Nudge, receiver: Contact): Boolean
/**
@ -143,7 +142,6 @@ public interface IMirai : LowLevelApiAccessor {
*
* @see Image.queryUrl [Image] 的扩展函数
*/
@JvmBlockingBridge
public suspend fun queryImageUrl(bot: Bot, image: Image): String
/**
@ -151,7 +149,6 @@ public interface IMirai : LowLevelApiAccessor {
*
* @since 2.1
*/
@JvmBlockingBridge
public suspend fun queryProfile(bot: Bot, targetId: Long): UserProfile
/**
@ -170,13 +167,27 @@ public interface IMirai : LowLevelApiAccessor {
originalMessage: MessageChain
): OfflineMessageSource
/**
* @since 2.3
*/
public suspend fun downloadLongMessage(
bot: Bot,
resourceId: String,
): MessageChain
/**
* @since 2.3
*/
public suspend fun downloadForwardMessage(
bot: Bot,
resourceId: String,
): List<ForwardMessage.Node>
/**
* 通过好友验证
*
* @param event 好友验证的事件对象
*/
@JvmBlockingBridge
public suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent)
/**
@ -185,7 +196,6 @@ public interface IMirai : LowLevelApiAccessor {
* @param event 好友验证的事件对象
* @param blackList 拒绝后是否拉入黑名单
*/
@JvmBlockingBridge
public suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean = false)
/**
@ -193,7 +203,6 @@ public interface IMirai : LowLevelApiAccessor {
*
* @param event 加群验证的事件对象
*/
@JvmBlockingBridge
public suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent)
/**
@ -202,7 +211,6 @@ public interface IMirai : LowLevelApiAccessor {
* @param event 加群验证的事件对象
* @param blackList 拒绝后是否拉入黑名单
*/
@JvmBlockingBridge
public suspend fun rejectMemberJoinRequest(
event: MemberJoinRequestEvent,
blackList: Boolean = false,
@ -213,7 +221,6 @@ public interface IMirai : LowLevelApiAccessor {
* 获取在线的 [OtherClient] 列表
* @param mayIncludeSelf 服务器返回的列表可能包含 [Bot] 自己. [mayIncludeSelf] `false` 会排除自己
*/
@JvmBlockingBridge
public suspend fun getOnlineOtherClientsList(
bot: Bot,
mayIncludeSelf: Boolean = false
@ -225,7 +232,6 @@ public interface IMirai : LowLevelApiAccessor {
* @param event 加群验证的事件对象
* @param blackList 忽略后是否拉入黑名单
*/
@JvmBlockingBridge
public suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false)
/**
@ -233,7 +239,6 @@ public interface IMirai : LowLevelApiAccessor {
*
* @param event 邀请入群的事件对象
*/
@JvmBlockingBridge
public suspend fun acceptInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
/**
@ -241,7 +246,6 @@ public interface IMirai : LowLevelApiAccessor {
*
* @param event 邀请入群的事件对象
*/
@JvmBlockingBridge
public suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
}

View File

@ -36,6 +36,7 @@ public annotation class LowLevelApi
* **警告**: 所有的低级 API 都可能在任意时刻不经过任何警告和迭代就被修改. 因此非常不建议在任何情况下使用这些 API.
*/
@LowLevelApi
@JvmBlockingBridge
public interface LowLevelApiAccessor {
/**
* 主动刷新 keys, SKey, PSKey .
@ -70,7 +71,6 @@ public interface LowLevelApiAccessor {
* @see recallMessage
*/
@LowLevelApi
@JvmBlockingBridge
public suspend fun recallGroupMessageRaw(
bot: Bot,
groupCode: Long,
@ -83,7 +83,6 @@ public interface LowLevelApiAccessor {
* @see recallMessage
*/
@LowLevelApi
@JvmBlockingBridge
public suspend fun recallFriendMessageRaw(
bot: Bot,
targetId: Long,
@ -97,7 +96,6 @@ public interface LowLevelApiAccessor {
* @see recallMessage
*/
@LowLevelApi
@JvmBlockingBridge
public suspend fun recallGroupTempMessageRaw(
bot: Bot,
groupUin: Long,
@ -111,7 +109,6 @@ public interface LowLevelApiAccessor {
* 向服务器查询群列表. 返回值高 32 bits uin, 32 bits groupCode
*/
@LowLevelApi
@JvmBlockingBridge
public suspend fun getRawGroupList(bot: Bot): Sequence<Long>
/**
@ -123,7 +120,6 @@ public interface LowLevelApiAccessor {
* @see IMirai.calculateGroupUinByGroupCode 使用 groupCode 计算 groupUin
*/
@LowLevelApi
@JvmBlockingBridge
public suspend fun getRawGroupMemberList(
bot: Bot,
groupUin: Long,
@ -137,7 +133,6 @@ public interface LowLevelApiAccessor {
*/
@LowLevelApi
@MiraiExperimentalApi
@JvmBlockingBridge
public suspend fun getRawGroupAnnouncements(
bot: Bot,
groupId: Long,
@ -151,7 +146,6 @@ public interface LowLevelApiAccessor {
* @return 公告的fid
*/
@LowLevelApi
@JvmBlockingBridge
@MiraiExperimentalApi
public suspend fun sendGroupAnnouncement(
bot: Bot,
@ -165,7 +159,6 @@ public interface LowLevelApiAccessor {
* @param fid [GroupAnnouncement.fid]
*/
@LowLevelApi
@JvmBlockingBridge
@MiraiExperimentalApi
public suspend fun deleteGroupAnnouncement(
bot: Bot,
@ -178,7 +171,6 @@ public interface LowLevelApiAccessor {
* @param fid [GroupAnnouncement.fid]
*/
@LowLevelApi
@JvmBlockingBridge
@MiraiExperimentalApi
public suspend fun getGroupAnnouncement(
bot: Bot,
@ -193,7 +185,6 @@ public interface LowLevelApiAccessor {
* page从0开始传入可以得到发言列表
*/
@LowLevelApi
@JvmBlockingBridge
@MiraiExperimentalApi
public suspend fun getRawGroupActiveData(bot: Bot, groupId: Long, page: Int = -1): GroupActiveData
@ -203,7 +194,6 @@ public interface LowLevelApiAccessor {
*/
@LowLevelApi
@MiraiExperimentalApi
@JvmBlockingBridge
public suspend fun getRawGroupHonorListData(
bot: Bot,
groupId: Long,
@ -214,7 +204,6 @@ public interface LowLevelApiAccessor {
/**
* 处理一个账号请求添加机器人为好友的事件
*/
@JvmBlockingBridge
@LowLevelApi
public suspend fun solveNewFriendRequestEvent(
bot: Bot,
@ -229,7 +218,6 @@ public interface LowLevelApiAccessor {
* 处理被邀请加入一个群请求事件
*/
@LowLevelApi
@JvmBlockingBridge
public suspend fun solveBotInvitedJoinGroupRequestEvent(
bot: Bot,
eventId: Long,
@ -242,7 +230,6 @@ public interface LowLevelApiAccessor {
* 处理账号请求加入群事件
*/
@LowLevelApi
@JvmBlockingBridge
public suspend fun solveMemberJoinRequestEvent(
bot: Bot,
eventId: Long,
@ -258,7 +245,6 @@ public interface LowLevelApiAccessor {
* 查询语音的下载连接
*/
@LowLevelApi
@JvmBlockingBridge
public suspend fun getGroupVoiceDownloadUrl(
bot: Bot,
md5: ByteArray,
@ -272,7 +258,6 @@ public interface LowLevelApiAccessor {
* @param anonymousId [AnonymousMember.anonymousId]
*/
@LowLevelApi
@JvmBlockingBridge
public suspend fun muteAnonymousMember(
bot: Bot,
anonymousId: String,

View File

@ -27,6 +27,7 @@ import java.io.InputStream
/**
* 联系对象, 即可以与 [Bot] 互动的对象. 包含 [用户][User], [][Group].
*/
@JvmBlockingBridge
public interface Contact : ContactOrBot, CoroutineScope {
/**
* 这个联系对象所属 [Bot].
@ -57,14 +58,12 @@ public interface Contact : ContactOrBot, CoroutineScope {
*
* @return 消息回执. [引用][MessageReceipt.quote] [撤回][MessageReceipt.recall] 这条消息.
*/
@JvmBlockingBridge
public suspend fun sendMessage(message: Message): MessageReceipt<Contact>
/**
* 发送纯文本消息
* @see sendMessage
*/
@JvmBlockingBridge
public suspend fun sendMessage(message: String): MessageReceipt<Contact> = this.sendMessage(message.toPlainText())
/**
@ -84,9 +83,9 @@ public interface Contact : ContactOrBot, CoroutineScope {
* @throws EventCancelledException 当发送消息事件被取消时抛出
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时抛出. (最大大小约为 20 MB, mirai 限制的大小为 30 MB)
*/
@JvmBlockingBridge
public suspend fun uploadImage(resource: ExternalResource): Image
@JvmBlockingBridge
public companion object {
/**
* 读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
@ -98,7 +97,6 @@ public interface Contact : ContactOrBot, CoroutineScope {
* @see FileCacheStrategy
*/
@JvmStatic
@JvmBlockingBridge
@JvmOverloads
public suspend fun <C : Contact> C.sendImage(
imageStream: InputStream,
@ -112,7 +110,6 @@ public interface Contact : ContactOrBot, CoroutineScope {
* @see FileCacheStrategy
*/
@JvmStatic
@JvmBlockingBridge
@JvmOverloads
public suspend fun <C : Contact> C.sendImage(
file: File,
@ -124,7 +121,6 @@ public interface Contact : ContactOrBot, CoroutineScope {
*
* @see Contact.sendMessage 最终调用, 发送消息.
*/
@JvmBlockingBridge
@JvmStatic
public suspend fun <C : Contact> C.sendImage(resource: ExternalResource): MessageReceipt<C> =
resource.sendAsImageTo(this)
@ -139,7 +135,6 @@ public interface Contact : ContactOrBot, CoroutineScope {
* @throws OverFileSizeMaxException
*/
@JvmStatic
@JvmBlockingBridge
@JvmOverloads
public suspend fun Contact.uploadImage(
imageStream: InputStream,
@ -152,7 +147,6 @@ public interface Contact : ContactOrBot, CoroutineScope {
* @throws OverFileSizeMaxException
*/
@JvmStatic
@JvmBlockingBridge
@JvmOverloads
public suspend fun Contact.uploadImage(
file: File,
@ -165,7 +159,6 @@ public interface Contact : ContactOrBot, CoroutineScope {
*/
@Throws(OverFileSizeMaxException::class)
@JvmStatic
@JvmBlockingBridge
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXTENSION_SHADOWED_BY_MEMBER")
@kotlin.internal.LowPriorityInOverloadResolution // for better Java API
public suspend fun Contact.uploadImage(resource: ExternalResource): Image = this.uploadImage(resource)

View File

@ -31,6 +31,7 @@ import net.mamoe.mirai.message.data.toPlainText
*
* @see FriendMessageEvent
*/
@JvmBlockingBridge
public interface Friend : User, CoroutineScope {
/**
* QQ 号码
@ -62,7 +63,6 @@ public interface Friend : User, CoroutineScope {
*
* @return 消息回执. [引用][MessageReceipt.quote] [撤回][MessageReceipt.recall] 这条消息.
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<Friend>
/**
@ -70,14 +70,12 @@ public interface Friend : User, CoroutineScope {
*
* @see FriendDeleteEvent 好友删除事件
*/
@JvmBlockingBridge
public suspend fun delete()
/**
* 发送纯文本消息
* @see sendMessage
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Friend> =
this.sendMessage(message.toPlainText())

View File

@ -25,6 +25,7 @@ import net.mamoe.mirai.utils.OverFileSizeMaxException
/**
* .
*/
@JvmBlockingBridge
public interface Group : Contact, CoroutineScope {
/**
* 群名称.
@ -123,7 +124,6 @@ public interface Group : Contact, CoroutineScope {
* @throws IllegalStateException 当机器人为群主时
* @return 退出成功时 true; 已经退出时 false
*/
@JvmBlockingBridge
public suspend fun quit(): Boolean
/**
@ -141,14 +141,12 @@ public interface Group : Contact, CoroutineScope {
*
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<Group>
/**
* 发送纯文本消息
* @see sendMessage
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Group> =
this.sendMessage(message.toPlainText())
@ -163,7 +161,6 @@ public interface Group : Contact, CoroutineScope {
* @throws EventCancelledException 当发送消息事件被取消
* @throws OverFileSizeMaxException 当语音文件过大而被服务器拒绝上传时. (最大大小约为 1 MB)
*/
@JvmBlockingBridge
public suspend fun uploadVoice(resource: ExternalResource): Voice
@ -175,7 +172,6 @@ public interface Group : Contact, CoroutineScope {
*
* @since 2.2
*/
@JvmBlockingBridge
public suspend fun setEssenceMessage(source: MessageSource): Boolean
public companion object {

View File

@ -34,6 +34,7 @@ import net.mamoe.mirai.utils.WeakRefProperty
* - [Member.asFriend] 转换为 [Friend]
* - [Member.asStranger] 转换为 [Stranger]
*/
@JvmBlockingBridge
public interface Member : User {
/**
* 所在的群.
@ -79,7 +80,6 @@ public interface Member : User {
*
* @see Member.mute 支持 Kotlin [kotlin.time.Duration] 的扩展
*/
@JvmBlockingBridge
public suspend fun mute(durationSeconds: Int)
/**

View File

@ -11,6 +11,7 @@
package net.mamoe.mirai.contact
import kotlinx.serialization.Serializable
import net.mamoe.mirai.Bot
import kotlin.internal.InlineOnly

View File

@ -27,6 +27,7 @@ import kotlin.time.ExperimentalTime
* 群成员可能也是好友, 但他们在对象类型上不同.
* 群成员可以通过 [asFriend] 得到相关好友对象.
*/
@JvmBlockingBridge
public interface NormalMember : Member {
/**
* 群名片. 可能为空.
@ -88,7 +89,6 @@ public interface NormalMember : Member {
*
* @throws PermissionDeniedException 无权限修改时抛出
*/
@JvmBlockingBridge
public suspend fun unmute()
/**
@ -100,7 +100,6 @@ public interface NormalMember : Member {
* @throws PermissionDeniedException 无权限修改时
*
*/
@JvmBlockingBridge
public suspend fun kick(message: String)
/**
@ -122,14 +121,12 @@ public interface NormalMember : Member {
*
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<NormalMember>
/**
* 发送纯文本消息
* @see sendMessage
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<NormalMember> =
this.sendMessage(message.toPlainText())

View File

@ -39,6 +39,7 @@ import net.mamoe.mirai.message.data.toPlainText
*
* @see StrangerMessageEvent
*/
@JvmBlockingBridge
public interface Stranger : User, CoroutineScope {
/**
* QQ 号码
@ -66,7 +67,6 @@ public interface Stranger : User, CoroutineScope {
*
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<Stranger>
/**
@ -74,14 +74,12 @@ public interface Stranger : User, CoroutineScope {
*
* @see StrangerRelationChangeEvent.Deleted 陌生人删除事件
*/
@JvmBlockingBridge
public suspend fun delete()
/**
* 发送纯文本消息
* @see sendMessage
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Stranger> =
this.sendMessage(message.toPlainText())

View File

@ -36,6 +36,7 @@ import net.mamoe.mirai.message.data.toPlainText
*
* 对于同一个 [Bot] 任何一个人的 [User] 实例都是单一的.
*/
@JvmBlockingBridge
public interface User : Contact, UserOrBot, CoroutineScope {
/**
* QQ 号码
@ -72,14 +73,12 @@ public interface User : Contact, UserOrBot, CoroutineScope {
*
* @return 消息回执. [引用][MessageReceipt.quote] [撤回][MessageReceipt.recall] 这条消息.
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<User>
/**
* 发送纯文本消息
* @see sendMessage
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<User> =
this.sendMessage(message.toPlainText())
@ -95,7 +94,6 @@ public interface User : Contact, UserOrBot, CoroutineScope {
*
* @since 2.1
*/
@JvmBlockingBridge
public suspend fun queryProfile(): UserProfile = Mirai.queryProfile(bot, this.id)
}

View File

@ -17,9 +17,16 @@ public interface FriendInfo : UserInfo {
public override val nick: String
public override val remark: String
public override var remark: String
}
@Deprecated(
"Moved to net.mamoe.mirai.internal.contact.FriendInfoImpl. Kept for binary compatibility.",
ReplaceWith("FriendInfoImpl", "net.mamoe.mirai.internal.contact.FriendInfoImpl"),
level = DeprecationLevel.HIDDEN
)
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@LowLevelApi
public open class FriendInfoImpl(
override val uin: Long,

View File

@ -28,10 +28,10 @@ import net.mamoe.mirai.internal.event.ListenerRegistry
import net.mamoe.mirai.internal.event.registerEventHandler
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.cast
import java.util.function.Consumer
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.suspendCoroutine
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass
@ -129,11 +129,12 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads internal con
*/
@JvmSynthetic
public fun filter(filter: suspend (event: BaseEvent) -> Boolean): EventChannel<BaseEvent> {
val parent = this
return object : EventChannel<BaseEvent>(baseEventClass, defaultCoroutineContext) {
private inline val innerThis get() = this
override fun <E : Event> (suspend (E) -> ListeningStatus).intercepted(): suspend (E) -> ListeningStatus {
return { ev ->
val thisIntercepted: suspend (E) -> ListeningStatus = { ev ->
val filterResult = try {
@Suppress("UNCHECKED_CAST")
baseEventClass.isInstance(ev) && filter(ev as BaseEvent)
@ -141,9 +142,10 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads internal con
if (e is ExceptionInEventChannelFilterException) throw e // wrapped by another filter
throw ExceptionInEventChannelFilterException(ev, innerThis, cause = e)
}
if (filterResult) this.invoke(ev)
if (filterResult) this@intercepted.invoke(ev)
else ListeningStatus.LISTENING
}
return parent.intercept(thisIntercepted)
}
}
}
@ -203,16 +205,7 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads internal con
* @see filter 获取更多信息
*/
public fun <E : Event> filterIsInstance(kClass: KClass<out E>): EventChannel<E> {
return object : EventChannel<E>(kClass, defaultCoroutineContext) {
private inline val innerThis get() = this
override fun <E1 : Event> (suspend (E1) -> ListeningStatus).intercepted(): suspend (E1) -> ListeningStatus {
return { ev ->
if (kClass.isInstance(ev)) this.invoke(ev)
else ListeningStatus.LISTENING
}
}
}
return filter { kClass.isInstance(it) }.cast()
}
/**
@ -229,11 +222,17 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads internal con
*
* 此操作不会修改 [`this.coroutineContext`][defaultCoroutineContext], 只会创建一个新的 [EventChannel].
*/
public fun context(vararg coroutineContexts: CoroutineContext): EventChannel<BaseEvent> =
EventChannel(
public fun context(vararg coroutineContexts: CoroutineContext): EventChannel<BaseEvent> {
val origin = this
return object : EventChannel<BaseEvent>(
baseEventClass,
coroutineContexts.fold(this.defaultCoroutineContext) { acc, element -> acc + element }
)
) {
override fun <E : Event> (suspend (E) -> ListeningStatus).intercepted(): suspend (E) -> ListeningStatus {
return origin.intercept(this)
}
}
}
/**
* 创建一个新的 [EventChannel], [EventChannel] 包含 [this.coroutineContext][defaultCoroutineContext] 和添加的 [coroutineExceptionHandler]
@ -267,10 +266,7 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads internal con
* @see CoroutineScope.globalEventChannel `GlobalEventChannel.parentScope()` 的扩展
*/
public fun parentScope(coroutineScope: CoroutineScope): EventChannel<BaseEvent> {
return context(coroutineScope.coroutineContext).apply {
val job = coroutineScope.coroutineContext[Job]
if (job != null) parentJob(job)
}
return context(coroutineScope.coroutineContext)
}
/**
@ -512,10 +508,7 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads internal con
): Listener<E> = subscribeInternal(
eventClass.kotlin,
createListener(coroutineContext, concurrency, priority) { event ->
val context = currentCoroutineContext()
suspendCoroutine<Unit> { cont ->
Dispatchers.IO.dispatch(context) { cont.resumeWith(kotlin.runCatching { handler.accept(event) }) }
}
runInterruptible(Dispatchers.IO) { handler.accept(event) }
ListeningStatus.LISTENING
}
)
@ -542,10 +535,7 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads internal con
): Listener<E> = subscribeInternal(
eventClass.kotlin,
createListener(coroutineContext, concurrency, priority) { event ->
val context = currentCoroutineContext()
suspendCoroutine { cont ->
Dispatchers.IO.dispatch(context) { cont.resumeWith(kotlin.runCatching { handler.apply(event) }) }
}
runInterruptible(Dispatchers.IO) { handler.apply(event) }
}
)
@ -570,10 +560,7 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads internal con
): Listener<E> = subscribeInternal(
eventClass.kotlin,
createListener(coroutineContext, concurrency, priority) { event ->
val context = currentCoroutineContext()
suspendCoroutine<Unit> { cont ->
Dispatchers.IO.dispatch(context) { cont.resumeWith(kotlin.runCatching { handler.accept(event) }) }
}
runInterruptible(Dispatchers.IO) { handler.accept(event) }
ListeningStatus.STOPPED
}
)
@ -590,7 +577,11 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads internal con
return this
}
internal fun <L : Listener<E>, E : Event> subscribeInternal(eventClass: KClass<out E>, listener: L): L {
private fun <E : Event> intercept(listener: (suspend (E) -> ListeningStatus)): suspend (E) -> ListeningStatus {
return listener.intercepted()
}
private fun <L : Listener<E>, E : Event> subscribeInternal(eventClass: KClass<out E>, listener: L): L {
with(GlobalEventListeners[listener.priority]) {
@Suppress("UNCHECKED_CAST")
val node = ListenerRegistry(listener as Listener<Event>, eventClass)

View File

@ -475,6 +475,7 @@ public open class MessageSubscribersBuilder<M : MessageEvent, out Ret, R : RR, R
internal suspend inline fun executeAndReply(m: M, replier: suspend M.(String) -> Any?): RR {
when (val message = replier(m, m.message.contentToString())) {
is Message -> m.subject.sendMessage(message)
null,
is Unit -> Unit
else -> m.subject.sendMessage(message.toString())
}
@ -485,6 +486,7 @@ public open class MessageSubscribersBuilder<M : MessageEvent, out Ret, R : RR, R
internal suspend inline fun executeAndQuoteReply(m: M, replier: suspend M.(String) -> Any?): RR {
when (val message = replier(m, m.message.contentToString())) {
is Message -> m.subject.sendMessage(m.message.quote() + message)
null,
is Unit -> Unit
else -> m.subject.sendMessage(m.message.quote() + message.toString())
}

View File

@ -25,7 +25,7 @@ import net.mamoe.mirai.utils.MiraiInternalApi
/**
* [Bot] 登录完成, 好友列表, 群组列表初始化完成
*/
public data class BotOnlineEvent internal constructor(
public data class BotOnlineEvent @MiraiInternalApi public constructor(
public override val bot: Bot
) : BotActiveEvent, AbstractEvent()
@ -55,7 +55,7 @@ public sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
/**
* 被挤下线. 默认不会自动重连. 可将 [reconnect] 改为 `true` 以重连.
*/
public data class Force internal constructor(
public data class Force @MiraiInternalApi public constructor(
public override val bot: Bot,
public val title: String,
public val message: String,
@ -67,7 +67,7 @@ public sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
* 被服务器断开
*/
@MiraiInternalApi("This is very experimental and might be changed")
public data class MsfOffline internal constructor(
public data class MsfOffline @MiraiInternalApi public constructor(
public override val bot: Bot,
public override val cause: Throwable?
) : BotOfflineEvent(), Packet, BotPassiveEvent, CauseAware {
@ -77,7 +77,7 @@ public sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
/**
* 因网络问题而掉线
*/
public data class Dropped internal constructor(
public data class Dropped @MiraiInternalApi public constructor(
public override val bot: Bot,
public override val cause: Throwable?
) : BotOfflineEvent(), Packet, BotPassiveEvent, CauseAware {
@ -88,7 +88,7 @@ public sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
* returnCode = -10008 等原因掉线
*/
@MiraiInternalApi("This is very experimental and might be changed")
public data class PacketFactoryErrorCode internal constructor(
public data class PacketFactoryErrorCode @MiraiInternalApi public constructor(
val returnCode: Int,
public override val bot: Bot,
public override val cause: Throwable
@ -100,7 +100,7 @@ public sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
* 服务器主动要求更换另一个服务器
*/
@MiraiInternalApi
public data class RequireReconnect internal constructor(
public data class RequireReconnect @MiraiInternalApi public constructor(
public override val bot: Bot
) : BotOfflineEvent(), Packet, BotPassiveEvent {
override var reconnect: Boolean = true
@ -115,7 +115,7 @@ public sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
/**
* [Bot] 主动或被动重新登录. 在此事件广播前就已经登录完毕.
*/
public data class BotReloginEvent internal constructor(
public data class BotReloginEvent @MiraiInternalApi public constructor(
public override val bot: Bot,
public val cause: Throwable?
) : BotEvent, BotActiveEvent, AbstractEvent()

View File

@ -32,7 +32,7 @@ public data class FriendRemarkChangeEvent internal constructor(
public override val friend: Friend,
public val oldRemark: String,
public val newRemark: String
) : FriendEvent, Packet, AbstractEvent()
) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent
/**
* 成功添加了一个新好友的事件
@ -42,14 +42,14 @@ public data class FriendAddEvent @MiraiInternalApi constructor(
* 新好友. 已经添加到 [Bot.friends]
*/
public override val friend: Friend
) : FriendEvent, Packet, AbstractEvent()
) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent
/**
* 好友已被删除或主动删除的事件.
*/
public data class FriendDeleteEvent internal constructor(
public override val friend: Friend
) : FriendEvent, Packet, AbstractEvent()
) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent
/**
* 一个账号请求添加机器人为好友的事件
@ -77,7 +77,7 @@ public data class NewFriendRequestEvent internal constructor(
* 群名片或好友昵称
*/
public val fromNick: String
) : BotEvent, Packet, AbstractEvent() {
) : BotEvent, Packet, AbstractEvent(), FriendInfoChangeEvent {
@JvmField
internal val responded: AtomicBoolean = AtomicBoolean(false)
@ -109,7 +109,7 @@ public data class FriendNickChangedEvent internal constructor(
public override val friend: Friend,
public val from: String,
public val to: String
) : FriendEvent, Packet, AbstractEvent()
) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent
/**
* 好友输入状态改变的事件当开始输入文字退出聊天窗口或清空输入框时会触发此事件

View File

@ -9,7 +9,9 @@
@file:JvmMultifileClass
@file:JvmName("BotEventsKt")
@file:Suppress("unused", "FunctionName", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "DEPRECATION_ERROR")
@file:Suppress("unused", "FunctionName", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "DEPRECATION_ERROR",
"MemberVisibilityCanBePrivate"
)
package net.mamoe.mirai.event.events
@ -28,8 +30,8 @@ import java.util.concurrent.atomic.AtomicBoolean
/**
* 机器人被踢出群或在其他客户端主动退出一个群. 在事件广播前 [Bot.groups] 就已删除这个群.
*/
public sealed class BotLeaveEvent : BotEvent, Packet, AbstractEvent() {
public abstract val group: Group
public sealed class BotLeaveEvent : BotEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent {
public abstract override val group: Group
/**
* 机器人主动退出一个群.
@ -64,7 +66,7 @@ public data class BotGroupPermissionChangeEvent @MiraiInternalApi constructor(
public override val group: Group,
public val origin: MemberPermission,
public val new: MemberPermission
) : BotPassiveEvent, GroupEvent, Packet, AbstractEvent()
) : BotPassiveEvent, GroupEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent
/**
* Bot 被禁言
@ -75,7 +77,7 @@ public data class BotMuteEvent @MiraiInternalApi constructor(
* 操作人.
*/
public val operator: NormalMember
) : GroupEvent, Packet, BotPassiveEvent, AbstractEvent() {
) : GroupEvent, Packet, BotPassiveEvent, AbstractEvent(), GroupMemberInfoChangeEvent {
public override val group: Group
get() = operator.group
}
@ -88,7 +90,7 @@ public data class BotUnmuteEvent @MiraiInternalApi constructor(
* 操作人.
*/
public val operator: NormalMember
) : GroupEvent, Packet, BotPassiveEvent, AbstractEvent() {
) : GroupEvent, Packet, BotPassiveEvent, AbstractEvent(), GroupMemberInfoChangeEvent {
public override val group: Group
get() = operator.group
}
@ -96,7 +98,7 @@ public data class BotUnmuteEvent @MiraiInternalApi constructor(
/**
* Bot 成功加入了一个新群
*/
public sealed class BotJoinGroupEvent : GroupEvent, BotPassiveEvent, Packet, AbstractEvent() {
public sealed class BotJoinGroupEvent : GroupEvent, BotPassiveEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent {
public abstract override val group: Group
/**
@ -164,7 +166,7 @@ public data class GroupNameChangeEvent @MiraiInternalApi constructor(
* 操作人. null 时则是机器人操作
*/
public override val operator: NormalMember?
) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent()
) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent
/**
* 入群公告改变. 此事件广播前修改就已经完成.
@ -177,7 +179,7 @@ public data class GroupEntranceAnnouncementChangeEvent @MiraiInternalApi constru
* 操作人. null 时则是机器人操作
*/
public override val operator: NormalMember?
) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent()
) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent
/**
@ -191,7 +193,7 @@ public data class GroupMuteAllEvent @MiraiInternalApi constructor(
* 操作人. null 时则是机器人操作
*/
public override val operator: NormalMember?
) : GroupSettingChangeEvent<Boolean>, Packet, GroupOperableEvent, AbstractEvent()
) : GroupSettingChangeEvent<Boolean>, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent
/**
@ -205,7 +207,7 @@ public data class GroupAllowAnonymousChatEvent @MiraiInternalApi constructor(
* 操作人. null 时则是机器人操作
*/
public override val operator: NormalMember?
) : GroupSettingChangeEvent<Boolean>, Packet, GroupOperableEvent, AbstractEvent()
) : GroupSettingChangeEvent<Boolean>, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent
/**
@ -216,7 +218,7 @@ public data class GroupAllowConfessTalkEvent @MiraiInternalApi constructor(
public override val new: Boolean,
public override val group: Group,
public val isByBot: Boolean // 无法获取操作人
) : GroupSettingChangeEvent<Boolean>, Packet, AbstractEvent()
) : GroupSettingChangeEvent<Boolean>, Packet, AbstractEvent(), GroupMemberInfoChangeEvent
/**
* "允许群员邀请好友加群" 功能状态改变. 此事件广播前修改就已经完成.
@ -229,7 +231,7 @@ public data class GroupAllowMemberInviteEvent @MiraiInternalApi constructor(
* 操作人. null 时则是机器人操作
*/
public override val operator: NormalMember?
) : GroupSettingChangeEvent<Boolean>, Packet, GroupOperableEvent, AbstractEvent()
) : GroupSettingChangeEvent<Boolean>, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent
// endregion
@ -245,7 +247,7 @@ public data class GroupAllowMemberInviteEvent @MiraiInternalApi constructor(
public sealed class MemberJoinEvent(
public override val member: NormalMember
) : GroupMemberEvent, BotPassiveEvent, Packet,
AbstractEvent() {
AbstractEvent(), GroupMemberInfoChangeEvent {
/**
* 被邀请加入群
*/
@ -282,7 +284,7 @@ public sealed class MemberJoinEvent(
/**
* 成员已经离开群的事件. 在事件广播前成员就已经从 [Group.members] 中删除
*/
public sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent() {
public sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent(), GroupMemberInfoChangeEvent {
/**
* 成员被踢出群. 成员不可能是机器人自己.
*/
@ -320,13 +322,13 @@ public data class BotInvitedJoinGroupRequestEvent @MiraiInternalApi constructor(
* 邀请入群的账号的 id
*/
public val invitorId: Long,
public val groupId: Long,
public override val groupId: Long,
public val groupName: String,
/**
* 邀请人昵称
*/
public val invitorNick: String
) : BotEvent, Packet, AbstractEvent() {
) : BotEvent, Packet, AbstractEvent(), BaseGroupMemberInfoChangeEvent {
/**
* 邀请人. 若在事件发生后邀请人已经被删除好友, [invitor] `null`.
*/
@ -360,7 +362,7 @@ public data class MemberJoinRequestEvent @MiraiInternalApi constructor(
* 申请入群的账号的 id
*/
val fromId: Long,
val groupId: Long,
override val groupId: Long,
val groupName: String,
/**
* 申请人昵称
@ -370,7 +372,7 @@ public data class MemberJoinRequestEvent @MiraiInternalApi constructor(
* 邀请人 id如果是邀请入群
*/
val invitorId: Long? = null
) : BotEvent, Packet, AbstractEvent() {
) : BotEvent, Packet, AbstractEvent(), BaseGroupMemberInfoChangeEvent {
/**
* 相关群. 若在事件发生后机器人退出这个群, [group] `null`.
*/
@ -471,7 +473,7 @@ public data class MemberCardChangeEvent @MiraiInternalApi constructor(
public val new: String,
public override val member: NormalMember
) : GroupMemberEvent, Packet, AbstractEvent()
) : GroupMemberEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent
/**
* 成员群头衔改动. 一定为群主操作
@ -495,7 +497,7 @@ public data class MemberSpecialTitleChangeEvent @MiraiInternalApi constructor(
* null 时则是机器人操作.
*/
public override val operator: NormalMember?
) : GroupMemberEvent, GroupOperableEvent, AbstractEvent()
) : GroupMemberEvent, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent
// endregion
@ -509,7 +511,7 @@ public data class MemberPermissionChangeEvent @MiraiInternalApi constructor(
public override val member: NormalMember,
public val origin: MemberPermission,
public val new: MemberPermission
) : GroupMemberEvent, BotPassiveEvent, Packet, AbstractEvent()
) : GroupMemberEvent, BotPassiveEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent
// endregion
@ -528,7 +530,7 @@ public data class MemberMuteEvent @MiraiInternalApi constructor(
* 操作人. null 则为机器人操作
*/
public override val operator: Member?
) : GroupMemberEvent, Packet, GroupOperableEvent, AbstractEvent()
) : GroupMemberEvent, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent
/**
* 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人
@ -541,7 +543,7 @@ public data class MemberUnmuteEvent @MiraiInternalApi constructor(
* 操作人. null 则为机器人操作
*/
public override val operator: Member?
) : GroupMemberEvent, Packet, GroupOperableEvent, AbstractEvent()
) : GroupMemberEvent, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent
// endregion

View File

@ -16,6 +16,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.utils.MiraiInternalApi
/**
* 有关一个 [Bot] 的事件
@ -90,6 +91,8 @@ public interface FriendEvent : BotEvent, UserEvent {
override val user: Friend get() = friend
}
internal interface FriendInfoChangeEvent : BotEvent // for cache
/**
* 有关陌生人的事件
*/
@ -108,6 +111,19 @@ public interface GroupMemberEvent : GroupEvent, UserEvent {
override val user: Member get() = member
}
/**
* 用于更新缓存, 请勿使用.
*/
@MiraiInternalApi
internal interface BaseGroupMemberInfoChangeEvent : BotEvent {
val groupId: Long
} // for cache
@MiraiInternalApi
internal interface GroupMemberInfoChangeEvent : BotEvent, GroupEvent, BaseGroupMemberInfoChangeEvent {
override val groupId: Long get() = group.id
} // for cache
public interface OtherClientEvent : BotEvent, Packet {
public val client: OtherClient
override val bot: Bot get() = client.bot

View File

@ -76,6 +76,12 @@ internal class ListenerRegistry(
internal object GlobalEventListeners {
private val ALL_LEVEL_REGISTRIES: Map<EventPriority, ConcurrentLinkedQueue<ListenerRegistry>>
fun clear() {
ALL_LEVEL_REGISTRIES.forEach { (_, u) ->
u.clear()
}
}
init {
val map =
EnumMap<EventPriority, ConcurrentLinkedQueue<ListenerRegistry>>(EventPriority::class.java)

View File

@ -22,9 +22,10 @@ import kotlinx.serialization.modules.polymorphic
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.MessageChainImpl
import net.mamoe.mirai.utils.MiraiInternalApi
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
import kotlin.reflect.full.isSubclassOf
internal fun ClassSerialDescriptorBuilder.takeElementsFrom(descriptor: SerialDescriptor) {
@ -47,7 +48,6 @@ public open class MessageSourceSerializerImpl(serialName: String) :
takeElementsFrom(SerialData.serializer().descriptor)
},
serialize = {
// TODO: 2021-01-09 解决因为 originMessage 中 MessageSource 与 this 相同造成的死循环
SerialData(kind, botId, ids, internalIds, time, fromId, targetId, originalMessage)
},
deserialize = {
@ -71,42 +71,52 @@ public open class MessageSourceSerializerImpl(serialName: String) :
private val builtInSerializersModule by lazy {
SerializersModule {
// non-Message classes
contextual(RawForwardMessage::class, RawForwardMessage.serializer())
contextual(ForwardMessage.Node::class, ForwardMessage.Node.serializer())
contextual(VipFace.Kind::class, VipFace.Kind.serializer())
// NOTE: contextual serializers disabled because of https://github.com/mamoe/mirai/issues/951
// // non-Message classes
// contextual(RawForwardMessage::class, RawForwardMessage.serializer())
// contextual(ForwardMessage.Node::class, ForwardMessage.Node.serializer())
// contextual(VipFace.Kind::class, VipFace.Kind.serializer())
//
//
// // In case Proguard or something else obfuscated the Kotlin metadata, providing the serializers explicitly will help.
// contextual(At::class, At.serializer())
// contextual(AtAll::class, AtAll.serializer())
// contextual(CustomMessage::class, CustomMessage.serializer())
// contextual(CustomMessageMetadata::class, CustomMessageMetadata.serializer())
// contextual(Face::class, Face.serializer())
// contextual(Image::class, Image.Serializer)
// contextual(PlainText::class, PlainText.serializer())
// contextual(QuoteReply::class, QuoteReply.serializer())
//
// contextual(ForwardMessage::class, ForwardMessage.serializer())
//
//
// contextual(LightApp::class, LightApp.serializer())
// contextual(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
// contextual(AbstractServiceMessage::class, AbstractServiceMessage.serializer())
//
// contextual(PttMessage::class, PttMessage.serializer())
// contextual(Voice::class, Voice.serializer())
// contextual(PokeMessage::class, PokeMessage.serializer())
// contextual(VipFace::class, VipFace.serializer())
// contextual(FlashImage::class, FlashImage.serializer())
//
// contextual(MusicShare::class, MusicShare.serializer())
//
// contextual(MessageSource::class, MessageSource.serializer())
// In case Proguard or something else obfuscated the Kotlin metadata, providing the serializers explicitly will help.
contextual(At::class, At.serializer())
contextual(AtAll::class, AtAll.serializer())
contextual(CustomMessage::class, CustomMessage.serializer())
contextual(CustomMessageMetadata::class, CustomMessageMetadata.serializer())
contextual(Face::class, Face.serializer())
contextual(Image::class, Image.Serializer)
contextual(PlainText::class, PlainText.serializer())
contextual(QuoteReply::class, QuoteReply.serializer())
// contextual(SingleMessage::class, SingleMessage.Serializer)
contextual(MessageChain::class, MessageChain.Serializer)
contextual(MessageChainImpl::class, MessageChainImpl.serializer())
contextual(ForwardMessage::class, ForwardMessage.serializer())
contextual(ShowImageFlag::class, ShowImageFlag.Serializer)
contextual(LightApp::class, LightApp.serializer())
contextual(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
contextual(AbstractServiceMessage::class, AbstractServiceMessage.serializer())
contextual(PttMessage::class, PttMessage.serializer())
contextual(Voice::class, Voice.serializer())
contextual(PokeMessage::class, PokeMessage.serializer())
contextual(VipFace::class, VipFace.serializer())
contextual(FlashImage::class, FlashImage.serializer())
contextual(MusicShare::class, MusicShare.serializer())
contextual(MessageSource::class, MessageSource.serializer())
fun PolymorphicModuleBuilder<MessageMetadata>.messageMetadataSubclasses() {
subclass(MessageSource::class, MessageSource.serializer())
subclass(QuoteReply::class, QuoteReply.serializer())
subclass(ShowImageFlag::class, ShowImageFlag.Serializer)
}
fun PolymorphicModuleBuilder<MessageContent>.messageContentSubclasses() {
@ -131,13 +141,10 @@ private val builtInSerializersModule by lazy {
subclass(FlashImage::class, FlashImage.serializer())
subclass(MusicShare::class, MusicShare.serializer())
subclass(Dice::class, Dice.serializer())
}
contextual(SingleMessage::class, SingleMessage.Serializer)
contextual(MessageChain::class, MessageChain.Serializer)
contextual(MessageChainImpl::class, MessageChainImpl.serializer())
// polymorphicDefault(MessageChain::class) { MessageChainImpl.serializer() }
// polymorphic(SingleMessage::class) {
// subclass(MessageSource::class, MessageSource.serializer())
@ -154,6 +161,23 @@ private val builtInSerializersModule by lazy {
messageMetadataSubclasses()
}
polymorphic(MessageContent::class) {
messageContentSubclasses()
}
polymorphic(MessageMetadata::class) {
messageMetadataSubclasses()
}
polymorphic(RichMessage::class) {
subclass(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
subclass(LightApp::class, LightApp.serializer())
}
polymorphic(ServiceMessage::class) {
subclass(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
}
//contextual(SingleMessage::class, SingleMessage.Serializer)
// polymorphic(SingleMessage::class, SingleMessage.Serializer) {
// messageContentSubclasses()
@ -181,11 +205,16 @@ internal object MessageSerializersImpl : MessageSerializers {
override val serializersModule: SerializersModule get() = serializersModuleField ?: builtInSerializersModule
@Synchronized
override fun <M : SingleMessage> registerSerializer(baseClass: KClass<M>, serializer: KSerializer<M>) {
override fun <M : SingleMessage> registerSerializer(type: KClass<M>, serializer: KSerializer<M>) {
serializersModuleField = serializersModule.overwriteWith(SerializersModule {
contextual(baseClass, serializer)
polymorphic(SingleMessage::class) {
subclass(baseClass, serializer)
// contextual(type, serializer)
for (superclass in type.allSuperclasses) {
if (superclass.isFinal) continue
if (!superclass.isSubclassOf(SingleMessage::class)) continue
@Suppress("UNCHECKED_CAST")
polymorphic(superclass as KClass<Any>) {
subclass(type, serializer)
}
}
})
}

View File

@ -39,6 +39,7 @@ import net.mamoe.mirai.utils.MiraiInternalApi
* @see MessageReceipt.sourceIds ids
* @see MessageReceipt.sourceTime 源时间
*/
@JvmBlockingBridge
public open class MessageReceipt<out C : Contact> @MiraiInternalApi constructor(
/**
* 指代发送出去的消息.
@ -59,7 +60,6 @@ public open class MessageReceipt<out C : Contact> @MiraiInternalApi constructor(
*
* @see IMirai.recallMessage
*/
@JvmBlockingBridge
public suspend inline fun recall() {
return Mirai.recallMessage(target.bot, source)
}
@ -82,7 +82,6 @@ public open class MessageReceipt<out C : Contact> @MiraiInternalApi constructor(
* 引用这条消息并回复.
* @see MessageSource.quote 引用一条消息
*/
@JvmBlockingBridge
public suspend inline fun quoteReply(message: Message): MessageReceipt<C> {
@Suppress("UNCHECKED_CAST")
return target.sendMessage(this.quote() + message) as MessageReceipt<C>
@ -92,7 +91,6 @@ public open class MessageReceipt<out C : Contact> @MiraiInternalApi constructor(
* 引用这条消息并回复.
* @see MessageSource.quote 引用一条消息
*/
@JvmBlockingBridge
public suspend inline fun quoteReply(message: String): MessageReceipt<C> {
return this.quoteReply(PlainText(message))
}

View File

@ -11,8 +11,11 @@ package net.mamoe.mirai.message
import kotlinx.serialization.ContextualSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.*
import kotlinx.serialization.modules.PolymorphicModuleBuilder
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.subclass
import net.mamoe.mirai.internal.message.MessageSerializersImpl
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
@ -24,14 +27,13 @@ import kotlin.reflect.KClass
/**
* 消息序列化器.
*
* [MessageSerializers] 存放 [SerializersModule], 用于协助 [SingleMessage.Serializer] 的多态序列化.
* [MessageSerializers] 存放 [SerializersModule], 用于协助 [SingleMessage] [PolymorphicSerializer] 的多态序列化.
*
* 要序列化一个 [MessageChain], 请使用内建的 [MessageChain.serializeToJsonString]
*
* @see serializersModule
*
*
* @see SingleMessage.Serializer
* @see MessageChain.Serializer
*
* @see MessageSerializers.INSTANCE
@ -50,21 +52,26 @@ public interface MessageSerializers {
public val serializersModule: SerializersModule
/**
* 注册一个 [SerializersModuleBuilder.contextual] [SingleMessage] 多态域的 [PolymorphicModuleBuilder.subclass].
* 注册 [serializer] [type] 的所有为 [SingleMessage] 子类型的超类型的多态域 [PolymorphicModuleBuilder.subclass]
*
* 相当于
* 实现:
* ```
* contextual(baseClass, serializer)
* polymorphic(SingleMessage::class) {
* subclass(baseClass, serializer)
* for (superclass in type.allSuperclasses) {
* if (superclass.isFinal) continue
* if (superclass.isSubclassOf(SingleMessage::class)) continue
* polymorphic(superclass) {
* subclass(type, serializer)
* }
* }
* ```
*
*
* 若要自己实现消息类型, 务必在这里注册对应序列化器, 否则在 [MessageChain.serializeToJsonString] 时将会出错.
*
* @since 2.0, revised 2.3
*/
@MiraiExperimentalApi
public fun <M : SingleMessage> registerSerializer(baseClass: KClass<M>, serializer: KSerializer<M>)
public fun <M : SingleMessage> registerSerializer(type: KClass<M>, serializer: KSerializer<M>)
/**
* 合并 [serializersModule] [MessageSerializers.serializersModule] 并覆盖.

View File

@ -25,13 +25,7 @@ internal fun String.parseMiraiCodeImpl(contact: Contact?): MessageChain = buildM
add(PlainText(origin.decodeMiraiCode()))
return@forEachMiraiCode
}
parser.argsRegex.matchEntire(args)
?.destructured
?.let {
parser.runCatching {
contact.mapper(it)
}.getOrNull()
}
parser.parse(contact, args)
?.let(::add)
?: add(PlainText(origin.decodeMiraiCode()))
}
@ -124,13 +118,80 @@ private object MiraiCodeParsers : Map<String, MiraiCodeParser> by mapOf(
},
"app" to MiraiCodeParser(Regex("""(.*)""")) { (content) ->
LightApp(content.decodeMiraiCode())
}
},
"dice" to MiraiCodeParser(Regex("""([1-6])""")) { (value) ->
Dice(value.toInt())
},
"musicshare" to MiraiCodeParser.DynamicParser(7) { args ->
val (kind, title, summary, jumpUrl, pictureUrl) = args
val musicUrl = args[5]
val brief = args[6]
MusicShare(MusicKind.valueOf(kind), title, summary, jumpUrl, pictureUrl, musicUrl, brief)
},
)
private class MiraiCodeParser(
val argsRegex: Regex,
val mapper: Contact?.(MatchResult.Destructured) -> Message?
)
// Visitable for test
internal sealed class MiraiCodeParser {
abstract fun parse(contact: Contact?, args: String): Message?
class RegexParser(
private val argsRegex: Regex,
private val mapper: Contact?.(MatchResult.Destructured) -> Message?
) : MiraiCodeParser() {
override fun parse(contact: Contact?, args: String): Message? =
argsRegex.matchEntire(args)
?.destructured
?.let {
runCatching {
contact.mapper(it)
}.getOrNull()
}
}
class DynamicParser(
private val minArgs: Int,
private val maxArgs: Int = minArgs,
private val parser: (Contact?.(args: Array<String>) -> Message?),
) : MiraiCodeParser() {
override fun parse(contact: Contact?, args: String): Message? {
val ranges = mutableListOf<IntRange>()
if (args.isNotEmpty()) {
var begin = 0
var pos = 0
val len = args.length
while (pos < len) {
when (args[pos]) {
'\\' -> pos += 2
',' -> {
ranges.add(begin..pos)
pos++
begin = pos
}
else -> pos++
}
}
ranges.add(begin..len)
}
if (ranges.size < minArgs) return null
if (ranges.size > maxArgs) return null
@Suppress("RemoveExplicitTypeArguments")
val args0 = Array<String>(ranges.size) { index ->
val range = ranges[index]
args.substring(range.first, range.last).decodeMiraiCode()
}
runCatching {
return parser(contact, args0)
}
return null
}
}
}
private fun MiraiCodeParser(
argsRegex: Regex,
mapper: Contact?.(MatchResult.Destructured) -> Message?
): MiraiCodeParser = MiraiCodeParser.RegexParser(argsRegex, mapper)
internal fun StringBuilder.appendStringAsMiraiCode(value: String): StringBuilder = apply {
value.forEach { char ->

View File

@ -0,0 +1,34 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress(
"MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE",
"NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE",
"INAPPLICABLE_JVM_NAME"
)
@file:JvmMultifileClass
@file:JvmName("MessageUtils")
package net.mamoe.mirai.message.data
/**
* 约束一个 [MessageChain] 中只存在这一种类型的元素. 新元素将会替换旧元素, 保持原顺序.
*
* 实现此接口的元素将会在连接时自动处理替换.
*
* 要获取有关键的信息, 查看 [MessageKey].
* 要获取有关约束的处理方式, 查看 [AbstractPolymorphicMessageKey].
*/
public interface ConstrainSingle : SingleMessage {
/**
* 用于判断是否为同一种元素的 [MessageKey]. 使用多态类型 [MessageKey] 最上层的 [MessageKey].
* @see MessageKey 查看更多信息
*/
public val key: MessageKey<*>
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE")
@file:JvmMultifileClass
@file:JvmName("MessageUtils")
package net.mamoe.mirai.message.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.safeCast
import org.jetbrains.annotations.Range
import kotlin.random.Random
import kotlin.random.nextInt
/**
* 骰子.
*
* @since 2.5
*/
@Serializable
@SerialName(Dice.SERIAL_NAME)
public data class Dice(
/**
* 骰子的点数. 范围为 1..6
*/
public val value: @Range(from = 1, to = 6) Int
) : MarketFace, CodableMessage {
init {
require(value in 1..6) { "Dice.value must be in 1 and 6 inclusive." }
}
@MiraiExperimentalApi
override val name: String
get() = "[骰子:$value]"
@MiraiExperimentalApi
override val id: Int
get() = 11464
@MiraiExperimentalApi
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append("[mirai:dice:").append(value).append(']')
}
override fun toString(): String = "[mirai:dice:$value]"
public companion object Key :
AbstractPolymorphicMessageKey<MarketFace, Dice>(MarketFace, { it.safeCast() }) {
public const val SERIAL_NAME: String = "Dice"
/**
* 创建随机点数的 [骰子][Dice]
*/
@JvmStatic
public fun random(): Dice = Dice(Random.nextInt(1..6))
}
}

View File

@ -15,7 +15,9 @@ import net.mamoe.mirai.utils.safeCast
/**
* 商城表情
*
* 目前不支持直接发送可保存接收到的来自官方客户端的商城表情然后转发.
* [Dice] 可以发送外, 目前不支持直接发送可保存接收到的来自官方客户端的商城表情然后转发.
*
* @see Dice
*/
public interface MarketFace : HummerMessage {
/**

View File

@ -21,9 +21,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.fold
import kotlinx.serialization.*
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.utils.safeCast
import kotlin.internal.LowPriorityInOverloadResolution
/**
@ -31,7 +29,7 @@ import kotlin.internal.LowPriorityInOverloadResolution
*
* [消息][Message] 分为
* - [SingleMessage]:
* - [MessageMetadata] 消息元数据, 即消息的属性. 包括: [消息来源][MessageSource], [引用回复][QuoteReply].
* - [MessageMetadata] 消息元数据, 即消息的属性. 包括: [消息来源][MessageSource], [引用回复][QuoteReply] .
* - [MessageContent] 含内容的消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] .
* - [MessageChain]: 不可变消息链, 链表形式链接的多个 [SingleMessage] 实例.
*
@ -39,6 +37,8 @@ import kotlin.internal.LowPriorityInOverloadResolution
*
* 请先根据实际需求确定需要的类型.
*
* 特别地, 要以字符串方式处理消息, 可使用 [contentToString] [content] 得到内容字符串.
*
*
* - [PlainText]: 纯文本
* - [Image]: 图片
@ -56,7 +56,7 @@ import kotlin.internal.LowPriorityInOverloadResolution
* ## 使用 [Message]
*
* ### Kotlin 使用 [Message]:
* 与使用 [String] 的使用非常类似.
* 与使用 [String] 的使用类似.
*
* - 比较 [SingleMessage] [String]:
* `if(message.content == "你好") friend.sendMessage(event)`
@ -69,11 +69,12 @@ import kotlin.internal.LowPriorityInOverloadResolution
* 但注意: 不能 `String + Message`. 只能 `Message + String`
*
*
* ### Java 使用 [Message]:
*
*
* ### 发送消息
* - 通过 [Contact] 中的成员函数: [Contact.sendMessage]
* - 通过 [Message] 的扩展函数: [Message.sendTo]
* - [MessageEvent] 中使用 [MessageEvent.reply] 等捷径
*
* @see PlainText 纯文本
* @see Image 图片
@ -89,6 +90,8 @@ import kotlin.internal.LowPriorityInOverloadResolution
* @see buildMessageChain 构造一个 [MessageChain]
*
* @see Contact.sendMessage 发送消息
*
* @suppress **注意:** [Message] 类型大多有隐藏的协议实现, 不能被第三方应用继承,
*/
public interface Message { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
@ -240,71 +243,6 @@ public suspend inline operator fun Message.plus(another: Flow<Message>): Message
another.fold(this) { acc, it -> acc + it }.toMessageChain()
/**
* 单个消息元素. 与之相对的是 [MessageChain], 是多个 [SingleMessage] 的集合.
*/
@Serializable(SingleMessage.Serializer::class)
public interface SingleMessage : Message { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
public object Serializer : KSerializer<SingleMessage> by PolymorphicSerializer(SingleMessage::class)
}
/**
* 消息元数据, 即不含内容的元素.
*
* 这种类型的 [Message] 只表示一条消息的属性. 其子类为 [MessageSource], [QuoteReply] [CustomMessageMetadata]
*
* 所有子类的 [contentToString] 都应该返回空字符串.
*
* 要获取详细信息, 查看 [MessageChain].
*
* @see MessageSource 消息源
* @see QuoteReply 引用回复
* @see CustomMessageMetadata 自定义元数据
*
* @see ConstrainSingle 约束一个 [MessageChain] 中只存在这一种类型的元素
*/
public interface MessageMetadata : SingleMessage { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
/**
* 返回空字符串
*/
override fun contentToString(): String = ""
}
/**
* 约束一个 [MessageChain] 中只存在这一种类型的元素. 新元素将会替换旧元素, 保持原顺序.
*
* 实现此接口的元素将会在连接时自动处理替换.
*
* 要获取有关键的信息, 查看 [MessageKey].
* 要获取有关约束的处理方式, 查看 [AbstractPolymorphicMessageKey].
*/
public interface ConstrainSingle : SingleMessage {
/**
* 用于判断是否为同一种元素的 [MessageKey]. 使用多态类型 [MessageKey] 最上层的 [MessageKey].
* @see MessageKey 查看更多信息
*/
public val key: MessageKey<*>
}
/**
* 带内容的消息.
*
* @see PlainText 纯文本
* @see At At 一个群成员.
* @see AtAll At 全体成员
* @see HummerMessage 一些特殊消息: [戳一戳][PokeMessage], [闪照][FlashImage]
* @see Image 图片
* @see RichMessage 富文本
* @see ServiceMessage 服务消息, JSON/XML
* @see Face 原生表情
* @see ForwardMessage 合并转发
* @see Voice 语音
*/
public interface MessageContent : SingleMessage { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
public companion object Key : AbstractMessageKey<MessageContent>({ it.safeCast() })
}
/**
* [Message.contentToString] 的捷径
*/

View File

@ -201,7 +201,7 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
*/
@JvmStatic
@JvmBlockingBridge
public suspend inline fun MessageChain.recall(): Unit = this.source.recall()
public suspend fun MessageChain.recall(): Unit = this.source.recall()
/**
* 在一段时间后撤回这条消息.

View File

@ -11,7 +11,10 @@
package net.mamoe.mirai.message.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.message.code.internal.appendStringAsMiraiCode
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.safeCast
@ -22,6 +25,7 @@ import net.mamoe.mirai.utils.safeCast
* @since 2.1
*/
@Serializable
@SerialName(MusicShare.SERIAL_NAME)
public data class MusicShare(
/**
* 音乐应用类型
@ -51,7 +55,7 @@ public data class MusicShare(
* 在消息列表显示
*/
public val brief: String,
) : MessageContent, ConstrainSingle {
) : MessageContent, ConstrainSingle, CodableMessage {
public constructor(
/**
@ -86,6 +90,19 @@ public data class MusicShare(
override fun contentToString(): String =
brief.takeIf { it.isNotBlank() } ?: "[分享]$title" // empty content is not accepted by `sendMessage`
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append("[mirai:musicshare:")
.append(kind.name)
.append(',').appendStringAsMiraiCode(title)
.append(',').appendStringAsMiraiCode(summary)
.append(',').appendStringAsMiraiCode(jumpUrl)
.append(',').appendStringAsMiraiCode(pictureUrl)
.append(',').appendStringAsMiraiCode(musicUrl)
.append(',').appendStringAsMiraiCode(brief)
.append(']')
}
// MusicShare(type=NeteaseCloudMusic, title='ファッション', summary='rinahamu/Yunomi', brief='', url='http://music.163.com/song/1338728297/?userid=324076307', pictureUrl='http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg', musicUrl='http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307')
/**
@ -93,7 +110,13 @@ public data class MusicShare(
*/
public companion object Key :
AbstractPolymorphicMessageKey<@MiraiExperimentalApi MessageContent, MusicShare>
(MessageContent, { it.safeCast() })
(MessageContent, { it.safeCast() }) {
/**
* @since 2.3
*/
public const val SERIAL_NAME: String = "MusicShare"
}
}
/**

View File

@ -98,6 +98,9 @@ public interface RichMessage : MessageContent, ConstrainSingle {
@Serializable
@SerialName(LightApp.SERIAL_NAME)
public data class LightApp(override val content: String) : RichMessage, CodableMessage {
// implementation notes: LightApp is always decoded as LightAppInternal
// which are transformed as RefinableMessage to LightApp
public companion object Key : AbstractMessageKey<LightApp>({ it.safeCast() }) {
public const val SERIAL_NAME: String = "LightApp"
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package net.mamoe.mirai.message.data
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.IMirai
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.safeCast
/**
* 标识来源 [RichMessage], 存在于接收的 [MessageChain] . 在发送消息时会被忽略.
*
* 一些 [RichMessage] 会被 mirai 解析成特定的更易使用的类型, :
* - 长消息会被协议内部转化为 [ServiceMessage] `serviceId=35` 通过独立通道上传和下载并获得一个 [resourceId]. mirai 会自动下载长消息并把他们解析为 [MessageChain].
* - 合并转发也使用长消息通道传输, 拥有 [resourceId], mirai 解析为 [ForwardMessage]
* - [MusicShare] 也有特殊通道上传, 但会作为 [LightApp] 接收.
*
* 这些经过转换的类型的来源 [RichMessage] 会被包装为 [RichMessageOrigin] 并加入消息链中.
*
* 如一条被 mirai 解析的长消息的消息链组成为, 第一个元素为 [MessageSource], 第二个元素为 [RichMessageOrigin], 随后为长消息内容.
*
* 又如一条被 mirai 解析的 [MusicShare] 的消息链组成为, 第一个元素为 [MessageSource], 第二个元素为 [RichMessageOrigin], 第三个元素为 [MusicShare].
*
* @suppress **注意**: 这是实验性 API: 类名, 类的类型, 构造, 属性等所有 API 均不稳定. 可能会在未来任意时刻变更.
*
* @since 2.3
*/
@Serializable
@SerialName(RichMessageOrigin.SERIAL_NAME)
@MiraiExperimentalApi("RichMessageOrigin 不稳定")
public class RichMessageOrigin(
/**
* [RichMessage].
*/
public val origin: @Polymorphic RichMessage,
/**
* 如果来自长消息或转发消息, 则会有 [resourceId], 否则为 `null`.
*
* - 下载长消息 [IMirai.downloadLongMessage]
* - 下载合并转发消息 [IMirai.downloadForwardMessage]
*/
public val resourceId: String?,
/**
* 来源类型
*/
public val kind: RichMessageKind,
) : MessageMetadata, ConstrainSingle {
override val key: Key get() = Key
override fun toString(): String {
val resourceId = resourceId
return if (resourceId == null) "[mirai:origin:$kind]"
else "[mirai:origin:$kind,$resourceId]"
}
override fun contentToString(): String = ""
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as RichMessageOrigin
if (origin != other.origin) return false
if (resourceId != other.resourceId) return false
if (kind != other.kind) return false
return true
}
override fun hashCode(): Int {
var result = origin.hashCode()
result = 31 * result + (resourceId?.hashCode() ?: 0)
result = 31 * result + kind.hashCode()
return result
}
public companion object Key : AbstractMessageKey<RichMessageOrigin>({ it.safeCast() }) {
public const val SERIAL_NAME: String = "RichMessageOrigin"
}
}
/**
* 消息来源
*
* @suppress 随着更新, 元素数量会增加. 类名不稳定.
*
* @since 2.3
*/
@MiraiExperimentalApi("RichMessageKind 类名不稳定")
public enum class RichMessageKind {
/**
* 长消息
*/
LONG,
/**
* 合并转发
* @see ForwardMessage
*/
FORWARD,
/**
* 音乐分享
* @see MusicShare
* @since 2.4
*/
MUSIC_SHARE,
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.message.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.encoding.encodeStructure
import net.mamoe.mirai.utils.safeCast
/**
* [MessageChain] 中包含秀图时的标记.
*
* 秀图已被 QQ 弃用, 仅作识别处理
*
*
* ```
* MessageEvent event
*
* if (event.message.contains(ShowImageFlag)) {
* // event.message 包含的图片是作为 '秀图' 发送
* }
* ```
*
* @since 2.2
*/
@SerialName(ShowImageFlag.SERIAL_NAME)
@Serializable(ShowImageFlag.Serializer::class)
public object ShowImageFlag : MessageMetadata, ConstrainSingle, AbstractMessageKey<ShowImageFlag>({ it.safeCast() }) {
override val key: ShowImageFlag get() = this
override fun toString(): String = "ShowImageFlag"
/**
* @since 2.4
*/
public const val SERIAL_NAME: String = "ShowImageFlag"
/**
* @since 2.4
*/
internal object Serializer : KSerializer<ShowImageFlag> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor(SERIAL_NAME)
override fun deserialize(decoder: Decoder): ShowImageFlag {
decoder.decodeStructure(descriptor) {}
return ShowImageFlag
}
override fun serialize(encoder: Encoder, value: ShowImageFlag) {
encoder.encodeStructure(descriptor) {}
}
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress(
"MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE",
"NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE",
"INAPPLICABLE_JVM_NAME"
)
@file:JvmMultifileClass
@file:JvmName("MessageUtils")
package net.mamoe.mirai.message.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.PolymorphicSerializer
import net.mamoe.mirai.utils.safeCast
/**
* 单个消息元素. 与之相对的是 [MessageChain], 是多个 [SingleMessage] 的集合.
*/
// @Serializable(SingleMessage.Serializer::class)
public interface SingleMessage : Message { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
/**
* @suppress deprecated since 2.4.0
*/
@Deprecated(
"Please create PolymorphicSerializer(SingleMessage::class) on your own.",
ReplaceWith(
"PolymorphicSerializer(SingleMessage::class)",
"kotlinx.serialization.PolymorphicSerializer",
"net.mamoe.mirai.message.data.SingleMessage",
),
level = DeprecationLevel.WARNING
)
public object Serializer : KSerializer<SingleMessage> by PolymorphicSerializer(SingleMessage::class)
}
/**
* 消息元数据, 即不含内容的元素.
*
* 这种类型的 [Message] 只表示一条消息的属性. 其子类为 [MessageSource], [QuoteReply] [CustomMessageMetadata]
*
* 所有子类的 [contentToString] 都应该返回空字符串.
*
* 要获取详细信息, 查看 [MessageChain].
*
* @see MessageSource 消息源
* @see QuoteReply 引用回复
* @see CustomMessageMetadata 自定义元数据
* @see ShowImageFlag 秀图标识
*
* @see ConstrainSingle 约束一个 [MessageChain] 中只存在这一种类型的元素
*/
public interface MessageMetadata : SingleMessage { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
/**
* 返回空字符串
*/
override fun contentToString(): String = ""
}
/**
* 带内容的消息.
*
* @see PlainText 纯文本
* @see At At 一个群成员.
* @see AtAll At 全体成员
* @see HummerMessage 一些特殊消息: [戳一戳][PokeMessage], [闪照][FlashImage]
* @see Image 图片
* @see RichMessage 富文本
* @see ServiceMessage 服务消息, JSON/XML
* @see Face 原生表情
* @see ForwardMessage 合并转发
* @see Voice 语音
* @see MarketFace 商城表情
* @see MusicShare 音乐分享
*/
public interface MessageContent : SingleMessage { // TODO: 2021/1/10 Make sealed interface in Kotlin 1.5
public companion object Key : AbstractMessageKey<MessageContent>({ it.safeCast() })
}

View File

@ -77,5 +77,5 @@ public class Voice @MiraiInternalApi constructor(
public override fun toString(): String = _stringValue!!
public override fun contentToString(): String = "[语音]"
public override fun contentToString(): String = "[语音消息]"
}

View File

@ -1,25 +0,0 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.safeCast
/**
* [MessageChain] 中包含秀图时的标记
*
* 秀图已被 QQ 弃用, 仅作识别处理
*
* @since 2.2
*/
public object ShowImageFlag : MessageMetadata, ConstrainSingle, AbstractMessageKey<ShowImageFlag>({ it.safeCast() }) {
override val key: ShowImageFlag get() = this
override fun toString(): String = "ShowImageFlag"
}

View File

@ -25,6 +25,9 @@ import java.io.File
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.milliseconds
/**
* [Bot] 配置. 用于 [BotFactory.newBot]
@ -57,32 +60,87 @@ public open class BotConfiguration { // open for Java
public var workingDir: File = File(".")
/**
* 日志记录器
*
* - 默认打印到标准输出, 通过 [MiraiLogger.create]
* - 忽略所有日志: [noBotLog]
* - 重定向到一个目录: `networkLoggerSupplier = { DirectoryLogger("Net ${it.id}") }`
* - 重定向到一个文件: `networkLoggerSupplier = { SingleFileLogger("Net ${it.id}") }`
*
* @see MiraiLogger
* Json 序列化器, 使用 'kotlinx.serialization'
*/
public var botLoggerSupplier: ((Bot) -> MiraiLogger) = { MiraiLogger.create("Bot ${it.id}") }
@MiraiExperimentalApi
public var json: Json = kotlin.runCatching {
Json {
isLenient = true
ignoreUnknownKeys = true
prettyPrint = true
}
}.getOrElse { Json {} }
/**
* 网络层日志构造器
*
* - 默认打印到标准输出, 通过 [MiraiLogger.create]
* - 忽略所有日志: [noNetworkLog]
* - 重定向到一个目录: `networkLoggerSupplier = { DirectoryLogger("Net ${it.id}") }`
* - 重定向到一个文件: `networkLoggerSupplier = { SingleFileLogger("Net ${it.id}") }`
*
* @see MiraiLogger
*/
public var networkLoggerSupplier: ((Bot) -> MiraiLogger) = { MiraiLogger.create("Net ${it.id}") }
///////////////////////////////////////////////////////////////////////////
// Coroutines
///////////////////////////////////////////////////////////////////////////
/** 父 [CoroutineContext]. [Bot] 创建后会使用 [SupervisorJob] 覆盖其 [Job], 但会将这个 [Job] 作为父 [Job] */
public var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
/**
* 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext].
*
* Bot 将会使用一个 [SupervisorJob] 覆盖 [coroutineContext] 当前协程的 [Job], 并使用当前协程的 [Job] 作为父 [Job]
*
* 用例:
* ```
* coroutineScope {
* val bot = Bot(...) {
* inheritCoroutineContext()
* }
* bot.login()
* } // coroutineScope 会等待 Bot 退出
* ```
*
*
* **注意**: `bot.cancel` 时将会让父 [Job] 也被 cancel.
* ```
* coroutineScope { // this: CoroutineScope
* launch {
* while(isActive) {
* delay(500)
* println("I'm alive")
* }
* }
*
* val bot = Bot(...) {
* inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job
* }
* bot.login()
* bot.cancel() // 取消了整个 `coroutineScope`, 因此上文不断打印 `"I'm alive"` 的协程也会被取消.
* }
* ```
*
* 因此, 此函数尤为适合在 `suspend fun main()` 中使用, 它能阻止主线程退出:
* ```
* suspend fun main() {
* val bot = Bot() {
* inheritCoroutineContext()
* }
* bot.subscribe { ... }
*
* // 主线程不会退出, 直到 Bot 离线.
* }
* ```
*
* 简言之,
* - 若想让 [Bot] 作为 '守护进程' 运行, 则无需调用 [inheritCoroutineContext].
* - 若想让 [Bot] 依赖于当前协程, 让当前协程等待 [Bot] 运行, 则使用 [inheritCoroutineContext]
*
* @see parentCoroutineContext
*/
@JvmSynthetic
@ConfigurationDsl
public suspend inline fun inheritCoroutineContext() {
parentCoroutineContext = coroutineContext
}
///////////////////////////////////////////////////////////////////////////
// Connection
///////////////////////////////////////////////////////////////////////////
/** 心跳周期. 过长会导致被服务器断开连接. */
public var heartbeatPeriodMillis: Long = 60.secondsToMillis
@ -125,6 +183,26 @@ public open class BotConfiguration { // open for Java
/** 使用协议类型 */
public var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PHONE
public enum class MiraiProtocol {
/**
* Android 手机. 所有功能都支持.
*/
ANDROID_PHONE,
/**
* Android 平板.
*
* 注意: 不支持戳一戳事件解析
*/
ANDROID_PAD,
/**
* Android 手表.
*/
ANDROID_WATCH,
}
/**
* Highway 通道上传图片, 语音, 文件等资源时的协程数量.
*
@ -135,25 +213,6 @@ public open class BotConfiguration { // open for Java
*/
public var highwayUploadCoroutineCount: Int = Runtime.getRuntime().availableProcessors()
/**
* 设备信息覆盖. 在没有手动指定时将会通过日志警告, 并使用随机设备信息.
* @see fileBasedDeviceInfo 使用指定文件存储设备信息
* @see randomDeviceInfo 使用随机设备信息
*/
public var deviceInfo: ((Bot) -> DeviceInfo)? = deviceInfoStub // allows user to set `null` manually.
/**
* Json 序列化器, 使用 'kotlinx.serialization'
*/
@MiraiExperimentalApi
public var json: Json = kotlin.runCatching {
Json {
isLenient = true
ignoreUnknownKeys = true
prettyPrint = true
}
}.getOrElse { Json {} }
/**
* 设置 [autoReconnectOnForceOffline] `true`, 即在被挤下线时自动重连.
* @since 2.1
@ -163,6 +222,17 @@ public open class BotConfiguration { // open for Java
autoReconnectOnForceOffline = true
}
///////////////////////////////////////////////////////////////////////////
// Device
///////////////////////////////////////////////////////////////////////////
/**
* 设备信息覆盖. 在没有手动指定时将会通过日志警告, 并使用随机设备信息.
* @see fileBasedDeviceInfo 使用指定文件存储设备信息
* @see randomDeviceInfo 使用随机设备信息
*/
public var deviceInfo: ((Bot) -> DeviceInfo)? = deviceInfoStub // allows user to set `null` manually.
/**
* 使用随机设备信息.
*
@ -198,6 +268,34 @@ public open class BotConfiguration { // open for Java
deviceInfo = getFileBasedDeviceInfoSupplier { workingDir.resolve(filepath) }
}
///////////////////////////////////////////////////////////////////////////
// Logging
///////////////////////////////////////////////////////////////////////////
/**
* 日志记录器
*
* - 默认打印到标准输出, 通过 [MiraiLogger.create]
* - 忽略所有日志: [noBotLog]
* - 重定向到一个目录: `botLoggerSupplier = { DirectoryLogger("Bot ${it.id}") }`
* - 重定向到一个文件: `botLoggerSupplier = { SingleFileLogger("Bot ${it.id}") }`
*
* @see MiraiLogger
*/
public var botLoggerSupplier: ((Bot) -> MiraiLogger) = { MiraiLogger.create("Bot ${it.id}") }
/**
* 网络层日志构造器
*
* - 默认打印到标准输出, 通过 [MiraiLogger.create]
* - 忽略所有日志: [noNetworkLog]
* - 重定向到一个目录: `networkLoggerSupplier = { DirectoryLogger("Net ${it.id}") }`
* - 重定向到一个文件: `networkLoggerSupplier = { SingleFileLogger("Net ${it.id}") }`
*
* @see MiraiLogger
*/
public var networkLoggerSupplier: ((Bot) -> MiraiLogger) = { MiraiLogger.create("Net ${it.id}") }
/**
* 重定向 [网络日志][networkLoggerSupplier] 到指定目录. 若目录不存在将会自动创建 ([File.mkdirs])
@ -265,33 +363,6 @@ public open class BotConfiguration { // open for Java
botLoggerSupplier = { DirectoryLogger(identity(it), workingDir.resolve(dir), retain) }
}
public enum class MiraiProtocol {
/**
* Android 手机. 所有功能都支持.
*/
ANDROID_PHONE,
/**
* Android 平板.
*
* 注意: 不支持戳一戳事件解析
*/
ANDROID_PAD,
/**
* Android 手表.
*/
ANDROID_WATCH,
}
public companion object {
/** 默认的配置实例. 可以进行修改 */
@JvmStatic
public val Default: BotConfiguration = BotConfiguration()
}
/**
* 不显示网络日志. 不推荐.
* @see networkLoggerSupplier 更多日志处理方式
@ -310,86 +381,141 @@ public open class BotConfiguration { // open for Java
botLoggerSupplier = { _ -> SilentLogger }
}
///////////////////////////////////////////////////////////////////////////
// Cache
//////////////////////////////////////////////////////////////////////////
/**
* 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext].
*
* Bot 将会使用一个 [SupervisorJob] 覆盖 [coroutineContext] 当前协程的 [Job], 并使用当前协程的 [Job] 作为父 [Job]
*
* 用例:
* ```
* coroutineScope {
* val bot = Bot(...) {
* inheritCoroutineContext()
* }
* bot.login()
* } // coroutineScope 会等待 Bot 退出
* ```
*
*
* **注意**: `bot.cancel` 时将会让父 [Job] 也被 cancel.
* ```
* coroutineScope { // this: CoroutineScope
* launch {
* while(isActive) {
* delay(500)
* println("I'm alive")
* }
* }
*
* val bot = Bot(...) {
* inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job
* }
* bot.login()
* bot.cancel() // 取消了整个 `coroutineScope`, 因此上文不断打印 `"I'm alive"` 的协程也会被取消.
* }
* ```
*
* 因此, 此函数尤为适合在 `suspend fun main()` 中使用, 它能阻止主线程退出:
* ```
* suspend fun main() {
* val bot = Bot() {
* inheritCoroutineContext()
* }
* bot.subscribe { ... }
*
* // 主线程不会退出, 直到 Bot 离线.
* }
* ```
*
* 简言之,
* - 若想让 [Bot] 作为 '守护进程' 运行, 则无需调用 [inheritCoroutineContext].
* - 若想让 [Bot] 依赖于当前协程, 让当前协程等待 [Bot] 运行, 则使用 [inheritCoroutineContext]
*
* @see parentCoroutineContext
* 缓存数据目录, 相对于 [workingDir]
* @since 2.4
*/
@JvmSynthetic
@ConfigurationDsl
public suspend inline fun inheritCoroutineContext() {
parentCoroutineContext = coroutineContext
public var cacheDir: File = File("cache")
/**
* 联系人信息缓存配置. 将会保存在 [cacheDir] `contacts` 目录
* @since 2.4
*/
public var contactListCache: ContactListCache = ContactListCache()
/**
* 联系人信息缓存配置
* @see contactListCache
* @see enableContactCache
* @see disableContactCache
* @since 2.4
*/
public class ContactListCache {
/**
* 在有修改时自动保存间隔. 默认 60 . 在每次登录完成后有修改时都会立即保存一次.
*/
public var saveIntervalMillis: Long = 60_000
/**
* 在有修改时自动保存间隔. 默认 60 . 在每次登录完成后有修改时都会立即保存一次.
*/
@ExperimentalTime
public inline var saveInterval: Duration
@JvmSynthetic inline get() = saveIntervalMillis.milliseconds
@JvmSynthetic inline set(v) {
saveIntervalMillis = v.toLongMilliseconds()
}
/**
* 开启好友列表缓存.
*/
public var friendListCacheEnabled: Boolean = false
/**
* 开启群成员列表缓存.
*/
public var groupMemberListCacheEnabled: Boolean = false
}
/**
* 配置 [ContactListCache]
* ```
* contactListCache {
* saveIntervalMillis = 30_000
* friendListCacheEnabled = true
* }
* ```
* @since 2.4
*/
@JvmSynthetic
public inline fun contactListCache(action: ContactListCache.() -> Unit) {
action.invoke(this.contactListCache)
}
/**
* 禁用好友列表和群成员列表的缓存.
* @since 2.4
*/
@ConfigurationDsl
public fun disableContactCache() {
contactListCache.friendListCacheEnabled = false
contactListCache.groupMemberListCacheEnabled = false
}
/**
* 启用好友列表和群成员列表的缓存.
* @since 2.4
*/
@ConfigurationDsl
public fun enableContactCache() {
contactListCache.friendListCacheEnabled = true
contactListCache.groupMemberListCacheEnabled = true
}
///////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////
@Suppress("DuplicatedCode")
public fun copy(): BotConfiguration {
return BotConfiguration().also { new ->
new.botLoggerSupplier = botLoggerSupplier
new.networkLoggerSupplier = networkLoggerSupplier
new.deviceInfo = deviceInfo
// To structural order
new.workingDir = workingDir
new.json = json
new.parentCoroutineContext = parentCoroutineContext
new.heartbeatPeriodMillis = heartbeatPeriodMillis
new.heartbeatTimeoutMillis = heartbeatTimeoutMillis
new.firstReconnectDelayMillis = firstReconnectDelayMillis
new.reconnectPeriodMillis = reconnectPeriodMillis
new.reconnectionRetryTimes = reconnectionRetryTimes
new.autoReconnectOnForceOffline = autoReconnectOnForceOffline
new.loginSolver = loginSolver
new.protocol = protocol
new.highwayUploadCoroutineCount = highwayUploadCoroutineCount
new.deviceInfo = deviceInfo
new.botLoggerSupplier = botLoggerSupplier
new.networkLoggerSupplier = networkLoggerSupplier
new.cacheDir = cacheDir
new.contactListCache = contactListCache
new.convertLineSeparator = convertLineSeparator
}
}
/**
* 是否处理接受到的特殊换行符, 默认为 `true`
*
* - 若为 `true`, 会将收到的 `CRLF(\r\n)` `CR(\r)` 替换为 `LF(\n)`
* - 若为 `false`, 则不做处理
*
* @since 2.4
*/
@get:JvmName("isConvertLineSeparator")
public var convertLineSeparator: Boolean = true
/** 标注一个配置 DSL 函数 */
@Target(AnnotationTarget.FUNCTION)
@DslMarker
public annotation class ConfigurationDsl
public companion object {
/** 默认的配置实例. 可以进行修改 */
@JvmStatic
public val Default: BotConfiguration = BotConfiguration()
}
}
/**

View File

@ -43,6 +43,10 @@ public class DeviceInfo(
public val androidId: ByteArray get() = display
public val ipAddress: ByteArray get() = byteArrayOf(192.toByte(), 168.toByte(), 1, 123)
init {
require(imsiMd5.size == 16) { "Bad `imsiMd5.size`. Required 16, given ${imsiMd5.size}." }
}
@Transient
@MiraiInternalApi
public val guid: ByteArray = generateGuid(androidId, macAddress)

View File

@ -10,11 +10,27 @@
package net.mamoe.mirai.message.code
import net.mamoe.mirai.message.code.MiraiCode.deserializeMiraiCode
import net.mamoe.mirai.message.code.internal.MiraiCodeParser
import net.mamoe.mirai.message.data.*
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class TestMiraiCode {
@Test
fun testDynamicMiraiCodeParser() {
fun runTest(args: Int, code: String, parse: (args: Array<String>) -> Unit) {
val response = MiraiCodeParser.DynamicParser(args) { args0 -> parse(args0); AtAll }.parse(null, code)
assertNotNull(response, "Parser not invoked")
}
runTest(3, "test,\\,test,\\,\\,test") { (arg1, arg2, arg3) ->
assertEquals("test", arg1)
assertEquals(",test", arg2)
assertEquals(",,test", arg3)
}
runTest(2, ",") {}
}
@Test
fun testCodes() {
assertEquals(AtAll.toMessageChain(), "[mirai:atall]".deserializeMiraiCode())
@ -43,5 +59,19 @@ class TestMiraiCode {
+SimpleServiceMessage(1, "[HiHi!!!\\]")
+PlainText(" XE")
}, "[mirai:service:1,\\[HiHi!!!\\\\\\]] XE".deserializeMiraiCode())
assertEquals(buildMessageChain {
+Dice(1)
}, "[mirai:dice:1]".deserializeMiraiCode())
val musicShare = MusicShare(
kind = MusicKind.NeteaseCloudMusic,
title = "ファッション",
summary = "rinahamu/Yunomi",
jumpUrl = "http://music.163.com/song/1338728297/?userid=324076307",
pictureUrl = "http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg",
musicUrl = "http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307",
brief = "",
)
assertEquals(musicShare.toMessageChain(), musicShare.serializeToMiraiCode().deserializeMiraiCode())
}
}

View File

@ -9,17 +9,159 @@
package net.mamoe.mirai.event
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore
import net.mamoe.mirai.event.events.FriendEvent
import net.mamoe.mirai.event.events.GroupEvent
import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.internal.event.GlobalEventListeners
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
internal class EventChannelTest {
suspend fun suspendCall() {
}
data class TE(
val x: Int
) : AbstractEvent()
val semaphore = Semaphore(1)
@BeforeEach
fun x() {
runBlocking { semaphore.acquire() }
}
@AfterEach
fun s() {
GlobalEventListeners.clear()
runBlocking { semaphore.release() }
}
@Test
fun testFilter() {
runBlocking {
val received = suspendCoroutine<Int> { cont ->
GlobalEventChannel
.filterIsInstance<TE>()
.filter {
true
}
.filter {
it.x == 2
}
.filter {
true
}
.subscribeOnce<TE> {
cont.resume(it.x)
}
launch {
println("Broadcast 1")
TE(1).broadcast()
println("Broadcast 2")
TE(2).broadcast()
println("Broadcast done")
}
}
assertEquals(2, received)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testAsChannel() {
runBlocking {
val channel = GlobalEventChannel
.filterIsInstance<TE>()
.filter { true }
.filter { it.x == 2 }
.filter { true }
.asChannel(Channel.BUFFERED)
println("Broadcast 1")
TE(1).broadcast()
println("Broadcast 2")
TE(2).broadcast()
println("Broadcast done")
channel.close()
val list = channel.receiveAsFlow().toList()
assertEquals(1, list.size)
assertEquals(TE(2), list.single())
}
}
@Test
fun testExceptionInFilter() {
runBlocking {
assertFailsWith<ExceptionInEventChannelFilterException> {
suspendCoroutine<Int> { cont ->
GlobalEventChannel
.exceptionHandler {
cont.resumeWithException(it)
}
.filter {
error("test error")
}
.subscribeOnce<TE> {
cont.resume(it.x)
}
launch {
println("Broadcast 1")
TE(1).broadcast()
println("Broadcast done")
}
}
}.run {
assertEquals("test error", cause.message)
}
}
}
@Test
fun testExceptionInSubscribe() {
runBlocking {
assertFailsWith<IllegalStateException> {
suspendCoroutine<Int> { cont ->
GlobalEventChannel
.exceptionHandler {
cont.resumeWithException(it)
}
.subscribeOnce<TE> {
error("test error")
}
launch {
println("Broadcast 1")
TE(1).broadcast()
println("Broadcast done")
}
}
}.run {
assertEquals("test error", message)
}
}
}
@Suppress("UNUSED_VARIABLE")
@Test
fun testVariance() {

View File

@ -16,6 +16,7 @@ package net.mamoe.mirai.utils
import io.ktor.utils.io.charsets.*
import kotlinx.io.core.*
import java.io.File
import kotlin.text.Charsets
@ -123,4 +124,17 @@ public inline fun Input.readString(length: UShort, charset: Charset = Charsets.U
String(this.readBytes(length.toInt()), charset = charset)
public inline fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String =
String(this.readBytes(length.toInt()), charset = charset)
String(this.readBytes(length.toInt()), charset = charset)
public fun File.createFileIfNotExists() {
if (!this.exists()) {
this.parentFile.mkdirs()
this.createNewFile()
}
}
public fun File.resolveCreateFile(relative: String): File = this.resolve(relative).apply { createFileIfNotExists() }
public fun File.resolveCreateFile(relative: File): File = this.resolve(relative).apply { createFileIfNotExists() }
public fun File.resolveMkdir(relative: String): File = this.resolve(relative).apply { mkdirs() }
public fun File.resolveMkdir(relative: File): File = this.resolve(relative).apply { mkdirs() }

View File

@ -7,6 +7,10 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:JvmMultifileClass
@file:JvmName("MiraiUtils")
package net.mamoe.mirai.utils

View File

@ -0,0 +1,28 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:JvmMultifileClass
@file:JvmName("MiraiUtils")
package net.mamoe.mirai.utils
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat
import java.io.File
public fun <T> File.loadNotBlankAs(
serializer: DeserializationStrategy<T>,
stringFormat: StringFormat,
): T? {
if (!this.exists() || this.length() == 0L) {
return null
}
return stringFormat.decodeFromString(serializer, this.readText())
}

View File

@ -20,6 +20,13 @@ public inline fun <reified T> Any?.safeCast(): T? = this as? T
public inline fun <reified T> Any?.castOrNull(): T? = this as? T
public inline fun <reified R> Iterable<*>.firstIsInstanceOrNull(): R? {
for (it in this) {
if (it is R) return it
}
return null
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
@kotlin.internal.InlineOnly

View File

@ -7,6 +7,10 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:JvmMultifileClass
@file:JvmName("MiraiUtils")
@file:Suppress("unused", "NOTHING_TO_INLINE")
package net.mamoe.mirai.utils

View File

@ -28,6 +28,7 @@ import net.mamoe.mirai.event.EventPriority.MONITOR
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.BotReloginEvent
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.BotNetworkHandler
import net.mamoe.mirai.internal.network.DefaultServerList
import net.mamoe.mirai.internal.network.closeAndJoin
@ -68,7 +69,7 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
}
// region network
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
internal val serverList: MutableList<Pair<String, Int>> = mutableListOf()
val network: N get() = _network
@ -149,7 +150,10 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
bot.asQQAndroidBot().client.run {
if (serverList.isEmpty()) {
serverList.addAll(DefaultServerList)
bot.asQQAndroidBot().bdhSyncer.loadServerListFromCache()
if (serverList.isEmpty()) {
serverList.addAll(DefaultServerList)
} else Unit
} else serverList.removeAt(0)
}
@ -285,6 +289,17 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
reinitializeNetworkHandler(null)
}
// https://github.com/mamoe/mirai/issues/1019
kotlin.runCatching {
bot.nick
}.onFailure {
bot.asQQAndroidBot().nick = MiraiImpl.queryProfile(bot, bot.id).nickname
if (bot.nick.isBlank()) {
logger.warning { "Unable to fetch nickname of bot." }
}
}
logger.info { "Login successful" }
}
}
@ -322,6 +337,7 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
}
}
protected abstract suspend fun sendLogout()
override fun close(cause: Throwable?) {
if (!this.isActive) {
@ -329,14 +345,20 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
return
}
if (this.network.areYouOk()) {
GlobalScope.launch {
runCatching { BotOfflineEvent.Active(this@AbstractBot, cause).broadcast() }.exceptionOrNull()
?.let { logger.error(it) }
}
}
if (::_network.isInitialized) {
if (this.network.areYouOk()) {
this.network.close(cause)
// send log out
kotlin.runCatching { runBlocking { sendLogout() } } // just ignore errors
GlobalScope.launch {
runCatching { BotOfflineEvent.Active(this@AbstractBot, cause).broadcast() }.exceptionOrNull()
?.let { logger.error(it) }
}
}
this.network.close(cause)
}
if (supervisorJob.isActive) {
if (cause == null) {

View File

@ -33,7 +33,9 @@ internal data class BotAccount(
id,
passwordPlainText.md5(),
phoneNumber
)
) {
require(passwordPlainText.length <= 16) { "Password length must be at most 16." }
}
override fun equals(other: Any?): Boolean {
if (this === other) return true

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.internal
import contact.StrangerImpl
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.features.*
@ -16,6 +17,8 @@ import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.withContext
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.serialization.json.*
import net.mamoe.mirai.*
import net.mamoe.mirai.contact.*
@ -23,16 +26,21 @@ import net.mamoe.mirai.data.*
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.contact.*
import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.network.highway.Highway
import net.mamoe.mirai.internal.network.highway.ResourceKind
import net.mamoe.mirai.internal.network.highway.*
import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo
import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgTransmit
import net.mamoe.mirai.internal.network.protocol.packet.chat.*
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.action.Nudge
@ -785,6 +793,9 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
accept = accept,
blackList = blackList
).sendWithoutExpect()
if (!accept) return@apply
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.friends.delegate.add(newFriend(bot, FriendInfoImpl(fromId, fromNick, "")))
}
@ -959,4 +970,80 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
kind, ids, botId, time, fromId, targetId, originalMessage, internalIds
)
override suspend fun downloadLongMessage(bot: Bot, resourceId: String): MessageChain {
return downloadMultiMsgTransmit(bot, resourceId, ResourceKind.LONG_MESSAGE).msg
.toMessageChainNoSource(bot.id, 0, MessageSourceKind.GROUP)
}
override suspend fun downloadForwardMessage(bot: Bot, resourceId: String): List<ForwardMessage.Node> {
return downloadMultiMsgTransmit(bot, resourceId, ResourceKind.FORWARD_MESSAGE).msg.map { msg ->
ForwardMessage.Node(
senderId = msg.msgHead.fromUin,
time = msg.msgHead.msgTime,
senderName = msg.msgHead.groupInfo?.groupCard
?: msg.msgHead.fromNick.takeIf { it.isNotEmpty() }
?: msg.msgHead.fromUin.toString(),
messageChain = listOf(msg).toMessageChainNoSource(bot.id, 0, MessageSourceKind.GROUP)
)
}
}
private suspend fun downloadMultiMsgTransmit(
bot: Bot,
resourceId: String,
resourceKind: ResourceKind,
): MsgTransmit.PbMultiMsgTransmit {
bot.asQQAndroidBot()
when (val resp = MultiMsg.ApplyDown(bot.client, 2, resourceId, 1).sendAndExpect(bot)) {
is MultiMsg.ApplyDown.Response.RequireDownload -> {
val http = Mirai.Http
val origin = resp.origin
val data: ByteArray = if (origin.msgExternInfo?.channelType == 2) {
tryDownload(
bot = bot,
host = "https://ssl.htdata.qq.com",
port = 443,
times = 3,
resourceKind = resourceKind,
channelKind = ChannelKind.HTTP
) { host, _ ->
http.get("$host${origin.thumbDownPara}")
}
} else tryServersDownload(
bot = bot,
servers = origin.uint32DownIp.zip(origin.uint32DownPort),
resourceKind = resourceKind,
channelKind = ChannelKind.HTTP
) { ip, port ->
http.get("http://$ip:$port${origin.thumbDownPara}")
}
val body = data.read {
check(readByte() == 40.toByte()) {
"bad data while MultiMsg.ApplyDown: ${data.toUHexString()}"
}
val headLength = readInt()
val bodyLength = readInt()
discardExact(headLength)
readBytes(bodyLength)
}
val decrypted = TEA.decrypt(body, origin.msgKey)
val longResp =
decrypted.loadAs(LongMsg.RspBody.serializer())
val down = longResp.msgDownRsp.single()
check(down.result == 0) {
"Message download failed, result=${down.result}, resId=${down.msgResid}, msgContent=${down.msgContent.toUHexString()}"
}
val content = down.msgContent.ungzip()
return content.loadAs(MsgTransmit.PbMultiMsgTransmit.serializer())
}
MultiMsg.ApplyDown.Response.MessageTooLarge -> {
error("Message is too large and cannot download")
}
}
}
}

View File

@ -19,23 +19,26 @@ import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.*
import net.mamoe.mirai.internal.contact.OtherClientImpl
import net.mamoe.mirai.internal.contact.StrangerInfoImpl
import net.mamoe.mirai.internal.contact.checkIsGroupImpl
import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
import net.mamoe.mirai.internal.contact.uin
import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.*
import net.mamoe.mirai.internal.network.handler.BdhSessionSyncer
import net.mamoe.mirai.internal.network.handler.QQAndroidBotNetworkHandler
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
import net.mamoe.mirai.internal.network.protocol.packet.chat.*
import net.mamoe.mirai.internal.network.useNextServers
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.utils.ScheduledJob
import net.mamoe.mirai.internal.utils.friendCacheFile
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.*
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
import net.mamoe.mirai.internal.network.protocol.data.jce.FriendInfo as JceFriendInfo
import kotlin.time.milliseconds
internal fun Bot.asQQAndroidBot(): QQAndroidBot {
contract {
@ -56,7 +59,10 @@ internal class QQAndroidBot constructor(
private val account: BotAccount,
configuration: BotConfiguration
) : AbstractBot<QQAndroidBotNetworkHandler>(configuration, account.id) {
val bdhSyncer: BdhSessionSyncer = BdhSessionSyncer(this)
var client: QQAndroidClient = initClient()
private set
fun initClient(): QQAndroidClient {
client = QQAndroidClient(
@ -75,15 +81,44 @@ internal class QQAndroidBot constructor(
override val friends: ContactList<Friend> = ContactList()
override lateinit var nick: String
val friendListCache: FriendListCache? by lazy {
if (!configuration.contactListCache.friendListCacheEnabled) return@lazy null
val file = configuration.friendCacheFile()
val ret = file.loadNotBlankAs(FriendListCache.serializer(), JsonForCache) ?: FriendListCache()
internal var selfInfo: JceFriendInfo? = null
get() = field ?: error("selfInfo is not yet initialized")
set(it) {
checkNotNull(it)
field = it
nick = it.nick
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.eventChannel.parentScope(this@QQAndroidBot)
.subscribeAlways<net.mamoe.mirai.event.events.FriendInfoChangeEvent> {
friendListSaver?.notice()
}
ret
}
val groupMemberListCaches: GroupMemberListCaches? by lazy {
if (!configuration.contactListCache.groupMemberListCacheEnabled) {
return@lazy null
}
GroupMemberListCaches(this)
}
private val friendListSaver by lazy {
if (!configuration.contactListCache.friendListCacheEnabled) return@lazy null
ScheduledJob(coroutineContext, configuration.contactListCache.saveIntervalMillis.milliseconds) {
runBIO { saveFriendCache() }
}
}
fun saveFriendCache() {
val friendListCache = friendListCache ?: return
configuration.friendCacheFile().run {
createFileIfNotExists()
writeText(JsonForCache.encodeToString(FriendListCache.serializer(), friendListCache))
bot.network.logger.info { "Saved ${friendListCache.list.size} friends to local cache." }
}
}
override lateinit var nick: String
override val asFriend: Friend by lazy {
@OptIn(LowLevelApi::class)
@ -98,11 +133,18 @@ internal class QQAndroidBot constructor(
@ThisApiMustBeUsedInWithConnectionLockBlock
@Throws(LoginFailedException::class) // only
override suspend fun relogin(cause: Throwable?) {
bdhSyncer.loadFromCache()
client.useNextServers { host, port ->
network.closeEverythingAndRelogin(host, port, cause, 0)
}
}
override suspend fun sendLogout() {
network.run {
StatSvc.Register.offline(client).sendWithoutExpect()
}
}
override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler {
return QQAndroidBotNetworkHandler(coroutineContext, this)
}
@ -201,5 +243,5 @@ internal fun RichMessage.Key.forwardMessage(
<source name="${source.take(50)}" icon="" action="" appid="-1"/>
</msg>
""".trimIndent().replace("\n", " ")
return ForwardMessageInternal(template)
return ForwardMessageInternal(template, resId)
}

View File

@ -12,6 +12,7 @@ package net.mamoe.mirai.internal.contact
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.utils.cast
import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.unsafeWeakRef

View File

@ -18,12 +18,14 @@ import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.message.OfflineFriendImage
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.message.getImageType
import net.mamoe.mirai.internal.network.BdhSession
import net.mamoe.mirai.internal.network.highway.ChannelKind
import net.mamoe.mirai.internal.network.highway.Highway
import net.mamoe.mirai.internal.network.highway.ResourceKind.PRIVATE_IMAGE
import net.mamoe.mirai.internal.network.highway.postImage
import net.mamoe.mirai.internal.network.highway.tryServers
import net.mamoe.mirai.internal.network.highway.tryServersUpload
import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352
import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
@ -38,9 +40,6 @@ import kotlin.coroutines.CoroutineContext
internal val User.info: UserInfo? get() = this.castOrNull<AbstractUser>()?.info
internal open class UserInfoImpl(override val uin: Long, override val nick: String, override val remark: String = "") :
UserInfo
internal abstract class AbstractUser(
bot: Bot,
coroutineContext: CoroutineContext,
@ -133,7 +132,14 @@ internal abstract class AbstractUser(
resource = resource,
kind = PRIVATE_IMAGE,
commandId = 2,
initialTicket = response.uKey
initialTicket = response.uKey,
fallbackSession = {
BdhSession(
EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY,
ssoAddresses = response.uploadIpList.zip(response.uploadPortList)
.toMutableSet(),
)
}
)
}
}
@ -148,7 +154,7 @@ internal abstract class AbstractUser(
)
}.recoverCatchingSuppressed {
// try upload by http on provided servers
tryServers(
tryServersUpload(
bot = bot,
servers = resp.serverIp.zip(resp.serverPort),
resourceSize = resource.size,

View File

@ -23,10 +23,10 @@ import kotlinx.atomicfu.atomic
import net.mamoe.mirai.LowLevelApi
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.data.FriendInfo
import net.mamoe.mirai.data.FriendInfoImpl
import net.mamoe.mirai.event.events.FriendMessagePostSendEvent
import net.mamoe.mirai.event.events.FriendMessagePreSendEvent
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
import net.mamoe.mirai.internal.utils.C2CPkgMsgParsingCache
import net.mamoe.mirai.message.MessageReceipt
@ -42,15 +42,6 @@ internal fun net.mamoe.mirai.internal.network.protocol.data.jce.FriendInfo.toMir
remark
)
@OptIn(ExperimentalContracts::class)
internal inline fun FriendInfo.checkIsInfoImpl(): FriendInfoImpl {
contract {
returns() implies (this@checkIsInfoImpl is FriendInfoImpl)
}
check(this is FriendInfoImpl) { "A FriendInfo instance is not instance of checkIsInfoImpl. Your instance: ${this::class.qualifiedName}" }
return this
}
@OptIn(ExperimentalContracts::class)
internal inline fun Friend.checkIsFriendImpl(): FriendImpl {
contract {

View File

@ -20,12 +20,15 @@ import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.internal.network.BdhSession
import net.mamoe.mirai.internal.network.handler.QQAndroidBotNetworkHandler
import net.mamoe.mirai.internal.network.highway.*
import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_IMAGE
import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_VOICE
import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x388
import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopEssenceMsgManager
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
@ -46,9 +49,10 @@ internal fun GroupImpl.Companion.checkIsInstance(instance: Group) {
check(instance is GroupImpl) { "group is not an instanceof GroupImpl!! DO NOT interlace two or more protocol implementations!!" }
}
internal fun Group.checkIsGroupImpl() {
internal fun Group.checkIsGroupImpl(): GroupImpl {
contract { returns() implies (this@checkIsGroupImpl is GroupImpl) }
GroupImpl.checkIsInstance(this)
return this
}
@Suppress("PropertyName")
@ -166,7 +170,14 @@ internal class GroupImpl(
resource = resource,
kind = GROUP_IMAGE,
commandId = 2,
initialTicket = response.uKey
initialTicket = response.uKey,
noBdhAwait = true,
fallbackSession = {
BdhSession(
EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY,
ssoAddresses = response.uploadIpList.zip(response.uploadPortList).toMutableSet(),
)
},
)
return OfflineGroupImage(imageId = resource.calculateResourceId())
@ -191,7 +202,7 @@ internal class GroupImpl(
}.recoverCatchingSuppressed {
when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect()) {
is PttStore.GroupPttUp.Response.RequireUpload -> {
tryServers(
tryServersUpload(
bot,
resp.uploadIpList.zip(resp.uploadPortList),
resource.size,

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.internal.contact
import contact.StrangerImpl
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.nextEventOrNull
import net.mamoe.mirai.internal.MiraiImpl
@ -65,6 +66,13 @@ internal abstract class SendMessageHandler<C : Contact> {
groupCard = senderName // Cinnamon
) else null
// For ForwardMessage display
val ForwardMessage.INode.groupInfo: MsgComm.GroupInfo
get() = MsgComm.GroupInfo(
groupCode = if (isToGroup) targetGroupCode!! else 0,
groupCard = senderName
)
val isToGroup: Boolean get() = contact is Group
suspend fun MessageChain.convertToLongMessageIfNeeded(
@ -232,7 +240,9 @@ internal abstract class SendMessageHandler<C : Contact> {
* - ... any others for future
*/
internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessages(message: Message): MessageChain {
return message.takeSingleContent<ForwardMessage>()?.let { forward ->
suspend fun processForwardMessage(
forward: ForwardMessage
): ForwardMessageInternal {
if (!(message is MessageChain && message.contains(IgnoreLengthCheck))) {
check(forward.nodeList.size <= 200) {
throw MessageTooLargeException(
@ -248,12 +258,20 @@ internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessage
message = forward.nodeList,
isLong = false,
)
RichMessage.forwardMessage(
return RichMessage.forwardMessage(
resId = resId,
timeSeconds = currentTimeSeconds(),
forwardMessage = forward,
)
}?.toMessageChain() ?: message.toMessageChain()
}
fun processDice(dice: Dice): MarketFaceImpl {
return MarketFaceImpl(dice.toJceStruct())
}
return message.takeSingleContent<ForwardMessage>()?.let { processForwardMessage(it) }?.toMessageChain()
?: message.takeSingleContent<Dice>()?.let { processDice(it) }?.toMessageChain()
?: message.toMessageChain()
}
/**

View File

@ -15,17 +15,19 @@
"INVISIBLE_REFERENCE"
)
package net.mamoe.mirai.internal.contact
package contact
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import net.mamoe.mirai.LowLevelApi
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.FriendInfoImpl
import net.mamoe.mirai.data.StrangerInfo
import net.mamoe.mirai.event.events.StrangerMessagePostSendEvent
import net.mamoe.mirai.event.events.StrangerMessagePreSendEvent
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.AbstractUser
import net.mamoe.mirai.internal.contact.StrangerSendMessageHandler
import net.mamoe.mirai.internal.contact.sendMessageImpl
import net.mamoe.mirai.internal.message.OnlineMessageSourceToStrangerImpl
import net.mamoe.mirai.internal.network.protocol.packet.list.StrangerList
import net.mamoe.mirai.message.MessageReceipt
@ -36,20 +38,6 @@ import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
internal class StrangerInfoImpl(
override val uin: Long, override val nick: String, override val fromGroup: Long = 0,
override val remark: String = ""
) : StrangerInfo
@OptIn(ExperimentalContracts::class)
internal inline fun StrangerInfo.checkIsInfoImpl(): FriendInfoImpl {
contract {
returns() implies (this@checkIsInfoImpl is StrangerInfoImpl)
}
check(this is FriendInfoImpl) { "A StrangerInfo instance is not instance of StrangerInfoImpl. Your instance: ${this::class.qualifiedName}" }
return this
}
@OptIn(ExperimentalContracts::class)
internal inline fun Stranger.checkIsImpl(): StrangerImpl {
contract {

View File

@ -0,0 +1,21 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.contact.info
import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.FriendInfo
// since 2.4, for serialization
@Serializable
internal data class FriendInfoImpl(
override val uin: Long,
override var nick: String,
override var remark: String,
) : FriendInfo

View File

@ -0,0 +1,44 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.contact.info
import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum
@Serializable
internal data class GroupInfoImpl(
override val uin: Long,
override val owner: Long,
override val groupCode: Long,
override val memo: String,
override val name: String,
override val allowMemberInvite: Boolean,
override val allowAnonymousChat: Boolean,
override val autoApprove: Boolean,
override val confessTalk: Boolean,
override val muteAll: Boolean,
override val botMuteTimestamp: Int,
) : GroupInfo, Packet, Packet.NoLog {
constructor(stTroopNum: StTroopNum) : this(
uin = stTroopNum.groupUin,
owner = stTroopNum.dwGroupOwnerUin,
groupCode = stTroopNum.groupCode,
memo = stTroopNum.groupMemo,
name = stTroopNum.groupName,
allowMemberInvite = stTroopNum.dwGroupFlagExt?.and(0x000000c0) != 0L,
allowAnonymousChat = stTroopNum.dwGroupFlagExt?.and(0x40000000) == 0L,
autoApprove = stTroopNum.dwGroupFlagExt3?.and(0x00100000) == 0L,
confessTalk = stTroopNum.dwGroupFlagExt3?.and(0x00002000) == 0L,
muteAll = stTroopNum.dwShutUpTimestamp != 0L,
botMuteTimestamp = stTroopNum.dwMyShutUpTimestamp?.toInt() ?: 0,
)
}

View File

@ -7,15 +7,17 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.contact
package net.mamoe.mirai.internal.contact.info
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopMemberInfo
import net.mamoe.mirai.utils.currentTimeSeconds
internal class MemberInfoImpl(
@Serializable
internal data class MemberInfoImpl(
override val uin: Long,
override var nick: String,
override var permission: MemberPermission,
@ -27,7 +29,7 @@ internal class MemberInfoImpl(
override val joinTimestamp: Int = currentTimeSeconds().toInt(),
override var lastSpeakTimestamp: Int = 0,
override val isOfficialBot: Boolean = false
) : MemberInfo, UserInfoImpl(uin, nick, remark) {
) : MemberInfo {
constructor(
client: QQAndroidClient,
jceInfo: StTroopMemberInfo,

View File

@ -0,0 +1,23 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.contact.info
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.StrangerInfo
@SerialName("StrangerInfo")
@Serializable
internal class StrangerInfoImpl(
override val uin: Long,
override val nick: String,
override val fromGroup: Long = 0,
override val remark: String = ""
) : StrangerInfo

View File

@ -9,24 +9,89 @@
package net.mamoe.mirai.internal.message
import net.mamoe.mirai.message.data.AbstractPolymorphicMessageKey
import net.mamoe.mirai.message.data.AbstractServiceMessage
import net.mamoe.mirai.message.data.ServiceMessage
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.internal.asQQAndroidBot
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.safeCast
// internal runtime value, not serializable
internal data class LongMessageInternal internal constructor(override val content: String, val resId: String) :
AbstractServiceMessage() {
AbstractServiceMessage(), RefinableMessage {
override val serviceId: Int get() = 35
override suspend fun refine(contact: Contact, context: MessageChain): Message {
val bot = contact.bot.asQQAndroidBot()
val long = Mirai.downloadLongMessage(bot, resId)
return RichMessageOrigin(SimpleServiceMessage(serviceId, content), resId, RichMessageKind.LONG) + long
}
companion object Key :
AbstractPolymorphicMessageKey<ServiceMessage, LongMessageInternal>(ServiceMessage, { it.safeCast() })
}
// internal runtime value, not serializable
internal data class ForwardMessageInternal(override val content: String) : AbstractServiceMessage() {
@Suppress("RegExpRedundantEscape", "UnnecessaryVariable")
internal data class ForwardMessageInternal(override val content: String, val resId: String) : AbstractServiceMessage(),
RefinableMessage {
override val serviceId: Int get() = 35
override suspend fun refine(contact: Contact, context: MessageChain): Message {
val bot = contact.bot.asQQAndroidBot()
val msgXml = content.substringAfter("<msg", "")
val xmlHead = msgXml.substringBefore("<item")
val xmlFoot: String
val xmlContent = msgXml.substringAfter("<item").let {
xmlFoot = it.substringAfter("</item", "")
it.substringBefore("</item")
}
val brief = xmlHead.findField("brief")
val summary = SUMMARY_REGEX.find(xmlContent)?.let { it.groupValues[1] } ?: ""
val titles = TITLE_REGEX.findAll(xmlContent)
.map { it.groupValues[2].trim() }.toMutableList()
val title = titles.removeFirstOrNull() ?: ""
val preview = titles
val source = xmlFoot.findField("name")
return RichMessageOrigin(SimpleServiceMessage(serviceId, content), resId, RichMessageKind.FORWARD) + ForwardMessage(
preview = preview,
title = title,
brief = brief,
source = source,
summary = summary.trim(),
nodeList = Mirai.downloadForwardMessage(bot, resId)
)
}
companion object Key :
AbstractPolymorphicMessageKey<ServiceMessage, ForwardMessageInternal>(ServiceMessage, { it.safeCast() })
AbstractPolymorphicMessageKey<ServiceMessage, ForwardMessageInternal>(ServiceMessage, { it.safeCast() }) {
val SUMMARY_REGEX = """\<summary.*\>(.*?)\<\/summary\>""".toRegex()
@Suppress("SpellCheckingInspection")
val TITLE_REGEX = """\<title([A-Za-z\s#\"0-9\=]*)\>([\u0000-\uFFFF]*?)\<\/title\>""".toRegex()
fun String.findField(type: String): String {
return substringAfter("$type=\"", "")
.substringBefore("\"", "")
}
}
}
internal interface RefinableMessage : SingleMessage {
/**
* This message [RefinableMessage] will be replaced by return value of [refine]
*/
suspend fun refine(
contact: Contact,
context: MessageChain,
): Message?
}

View File

@ -0,0 +1,93 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.message
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.message.data.Dice
import net.mamoe.mirai.message.data.MarketFace
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
@SerialName(MarketFace.SERIAL_NAME)
@Serializable
internal data class MarketFaceImpl internal constructor(
internal val delegate: ImMsgBody.MarketFace,
) : MarketFace {
override val name: String get() = delegate.faceName.decodeToString()
@Transient
override val id: Int = delegate.tabId
override fun toString() = "[mirai:marketface:$id,$name]"
}
/**
* For refinement
*/
internal class MarketFaceInternal(
@JvmField private val delegate: ImMsgBody.MarketFace,
) : MarketFace, RefinableMessage {
override val name: String get() = delegate.faceName.decodeToString()
override val id: Int get() = delegate.tabId
override suspend fun refine(contact: Contact, context: MessageChain): Message {
delegate.toDiceOrNull()?.let { return it } // TODO: 2021/2/12 add dice origin, maybe rename RichMessageOrigin
return MarketFaceImpl(delegate)
}
override fun toString(): String = "[mirai:marketface:$id,$name]"
}
// From https://github.com/mamoe/mirai/issues/1012
internal fun Dice.toJceStruct(): ImMsgBody.MarketFace {
return ImMsgBody.MarketFace(
faceName = byteArrayOf(91, -23, -86, -80, -27, -83, -112, 93),
itemType = 6,
faceInfo = 1,
faceId = byteArrayOf(
72, 35, -45, -83, -79, 93,
-16, -128, 20, -50, 93, 103,
-106, -73, 110, -31
),
tabId = 11464,
subType = 3,
key = byteArrayOf(52, 48, 57, 101, 50, 97, 54, 57, 98, 49, 54, 57, 49, 56, 102, 57),
mediaType = 0,
imageWidth = 200,
imageHeight = 200,
mobileParam = byteArrayOf(
114, 115, 99, 84, 121, 112, 101,
63, 49, 59, 118, 97, 108, 117,
101, 61,
(47 + value).toByte()
),
pbReserve = byteArrayOf(
10, 6, 8, -56, 1, 16, -56, 1, 64,
1, 88, 0, 98, 9, 35, 48, 48, 48,
48, 48, 48, 48, 48, 106, 9, 35,
48, 48, 48, 48, 48, 48, 48, 48
)
)
}
private fun ImMsgBody.MarketFace.toDiceOrNull(): Dice? {
if (this.tabId != 11464) return null
val value = mobileParam.lastOrNull()?.toInt()?.and(0xff)?.minus(47) ?: 0
if (value in 1..6) {
return Dice(value)
}
return null
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.message
import kotlinx.serialization.Transient
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.sourceOrNull
import java.util.concurrent.atomic.AtomicBoolean
internal interface MessageSourceInternal {
@Transient
val sequenceIds: IntArray // ids
@Transient
val internalIds: IntArray // randomId
@Deprecated("don't use this internally. Use sequenceId or random instead.", level = DeprecationLevel.ERROR)
@Transient
val ids: IntArray
@Transient
val isRecalledOrPlanned: AtomicBoolean
fun toJceData(): ImMsgBody.SourceMsg
}
@Suppress("RedundantSuspendModifier", "unused")
internal suspend fun MessageSource.ensureSequenceIdAvailable() {
if (this is OnlineMessageSourceToGroupImpl) {
ensureSequenceIdAvailable()
}
}
@Suppress("RedundantSuspendModifier", "unused")
internal suspend inline fun Message.ensureSequenceIdAvailable() {
(this as? MessageChain)?.sourceOrNull?.ensureSequenceIdAvailable()
}

View File

@ -0,0 +1,492 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.message
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.internal.asQQAndroidBot
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.joinToMessageChain
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toVoice
import net.mamoe.mirai.internal.network.protocol.data.proto.CustomFace
import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.*
internal fun ImMsgBody.SourceMsg.toMessageChainNoSource(
botId: Long,
messageSourceKind: MessageSourceKind,
groupIdOrZero: Long
): MessageChain {
val elements = this.elems
return buildMessageChain(elements.size + 1) {
joinToMessageChain(elements, groupIdOrZero, messageSourceKind, botId, this)
}.cleanupRubbishMessageElements()
}
internal fun List<MsgComm.Msg>.toMessageChainOnline(
bot: Bot,
groupIdOrZero: Long,
messageSourceKind: MessageSourceKind
): MessageChain {
return toMessageChain(bot, bot.id, groupIdOrZero, true, messageSourceKind)
}
internal fun List<MsgComm.Msg>.toMessageChainOffline(
bot: Bot,
groupIdOrZero: Long,
messageSourceKind: MessageSourceKind
): MessageChain {
return toMessageChain(bot, bot.id, groupIdOrZero, false, messageSourceKind)
}
internal fun List<MsgComm.Msg>.toMessageChainNoSource(
botId: Long,
groupIdOrZero: Long,
messageSourceKind: MessageSourceKind
): MessageChain {
return toMessageChain(null, botId, groupIdOrZero, null, messageSourceKind)
}
private fun List<MsgComm.Msg>.toMessageChain(
bot: Bot?,
botId: Long,
groupIdOrZero: Long,
onlineSource: Boolean?,
messageSourceKind: MessageSourceKind
): MessageChain {
val messageList = this
val elements = messageList.flatMap { it.msgBody.richText.elems }
val builder = MessageChainBuilder(elements.size)
if (onlineSource != null) {
checkNotNull(bot)
builder.add(ReceiveMessageTransformer.createMessageSource(bot, onlineSource, messageSourceKind, messageList))
}
joinToMessageChain(elements, groupIdOrZero, messageSourceKind, botId, builder)
for (msg in messageList) {
msg.msgBody.richText.ptt?.toVoice()?.let { builder.add(it) }
}
return builder.build().cleanupRubbishMessageElements()
}
private object ReceiveMessageTransformer {
fun createMessageSource(
bot: Bot,
onlineSource: Boolean,
messageSourceKind: MessageSourceKind,
messageList: List<MsgComm.Msg>,
): MessageSource {
return when (onlineSource) {
true -> {
when (messageSourceKind) {
MessageSourceKind.TEMP -> OnlineMessageSourceFromTempImpl(bot, messageList)
MessageSourceKind.GROUP -> OnlineMessageSourceFromGroupImpl(bot, messageList)
MessageSourceKind.FRIEND -> OnlineMessageSourceFromFriendImpl(bot, messageList)
MessageSourceKind.STRANGER -> OnlineMessageSourceFromStrangerImpl(bot, messageList)
}
}
false -> {
OfflineMessageSourceImplData(bot, messageList, messageSourceKind)
}
}
}
fun joinToMessageChain(
elements: List<ImMsgBody.Elem>,
groupIdOrZero: Long,
messageSourceKind: MessageSourceKind,
botId: Long,
builder: MessageChainBuilder
) {
// (this._miraiContentToString().soutv())
for (element in elements) {
transformElement(element, groupIdOrZero, messageSourceKind, botId, builder)
when {
element.richMsg != null -> decodeRichMessage(element.richMsg, builder)
}
}
}
private fun transformElement(
element: ImMsgBody.Elem,
groupIdOrZero: Long,
messageSourceKind: MessageSourceKind,
botId: Long,
builder: MessageChainBuilder
) {
when {
element.srcMsg != null -> decodeSrcMsg(element.srcMsg, builder, botId, messageSourceKind, groupIdOrZero)
element.notOnlineImage != null -> builder.add(OnlineFriendImageImpl(element.notOnlineImage))
element.customFace != null -> decodeCustomFace(element.customFace, builder)
element.face != null -> builder.add(Face(element.face.index))
element.text != null -> decodeText(element.text, builder)
element.marketFace != null -> builder.add(MarketFaceInternal(element.marketFace))
element.lightApp != null -> decodeLightApp(element.lightApp, builder)
element.customElem != null -> decodeCustomElem(element.customElem, builder)
element.commonElem != null -> decodeCommonElem(element.commonElem, builder)
element.elemFlags2 != null
|| element.extraInfo != null
|| element.generalFlags != null -> {
// ignore
}
else -> {
// println(it._miraiContentToString())
}
}
}
fun MessageChainBuilder.compressContinuousPlainText() {
var index = 0
val builder = StringBuilder()
while (index + 1 < size) {
val elm0 = get(index)
val elm1 = get(index + 1)
if (elm0 is PlainText && elm1 is PlainText) {
builder.setLength(0)
var end = -1
for (i in index until size) {
val elm = get(i)
if (elm is PlainText) {
end = i
builder.append(elm.content)
} else break
}
set(index, PlainText(builder.toString()))
// do delete
val index1 = index + 1
repeat(end - index) {
removeAt(index1)
}
}
index++
}
}
fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
var previousLast: SingleMessage? = null
var last: SingleMessage? = null
return buildMessageChain(initialSize = this.count()) {
this@cleanupRubbishMessageElements.forEach { element ->
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
if (last is LongMessageInternal && element is PlainText) {
if (element == UNSUPPORTED_MERGED_MESSAGE_PLAIN) {
previousLast = last
last = element
return@forEach
}
}
if (last is PokeMessage && element is PlainText) {
if (element == UNSUPPORTED_POKE_MESSAGE_PLAIN) {
previousLast = last
last = element
return@forEach
}
}
if (last is VipFace && element is PlainText) {
val l = last as VipFace
if (element.content.length == 4 + (l.count / 10) + l.kind.name.length) {
previousLast = last
last = element
return@forEach
}
}
// 解决tim发送的语音无法正常识别
if (element is PlainText) {
if (element == UNSUPPORTED_VOICE_MESSAGE_PLAIN) {
previousLast = last
last = element
return@forEach
}
}
if (element is PlainText && last is At && previousLast is QuoteReply
&& element.content.startsWith(' ')
) {
// Android QQ 发送, 是 Quote+At+PlainText(" xxx") // 首空格
removeLastOrNull() // At
val new = PlainText(element.content.substring(1))
add(new)
previousLast = null
last = new
return@forEach
}
if (element is QuoteReply) {
// 客户端为兼容早期不支持 QuoteReply 的客户端而添加的 At
removeLastOrNull()?.let { rm ->
if ((rm as? PlainText)?.content != " ") add(rm)
else removeLastOrNull()?.let { rm2 ->
if (rm2 !is At) add(rm2)
}
}
}
append(element)
previousLast = last
last = element
}
// 处理分片信息
compressContinuousPlainText()
}
}
private fun decodeText(text: ImMsgBody.Text, list: MessageChainBuilder) {
if (text.attr6Buf.isEmpty()) {
list.add(PlainText(text.str))
} else {
val id: Long
text.attr6Buf.read {
discardExact(7)
id = readUInt().toLong()
}
if (id == 0L) {
list.add(AtAll)
} else {
list.add(At(id)) // element.text.str
}
}
}
private fun decodeSrcMsg(
srcMsg: ImMsgBody.SourceMsg,
list: MessageChainBuilder,
botId: Long,
messageSourceKind: MessageSourceKind,
groupIdOrZero: Long
) {
list.add(QuoteReply(OfflineMessageSourceImplData(srcMsg, botId, messageSourceKind, groupIdOrZero)))
}
private fun decodeCustomFace(
customFace: ImMsgBody.CustomFace,
builder: MessageChainBuilder,
) {
builder.add(OnlineGroupImageImpl(customFace))
customFace.pbReserve.let {
if (it.isNotEmpty() && it.loadAs(CustomFace.ResvAttr.serializer()).msgImageShow != null) {
builder.add(ShowImageFlag)
}
}
}
private fun decodeLightApp(
lightApp: ImMsgBody.LightAppElem,
list: MessageChainBuilder
) {
val content = runWithBugReport("解析 lightApp",
{ "resId=" + lightApp.msgResid + "data=" + lightApp.data.toUHexString() }) {
when (lightApp.data[0].toInt()) {
0 -> lightApp.data.encodeToString(offset = 1)
1 -> lightApp.data.unzip(1).encodeToString()
else -> error("unknown compression flag=${lightApp.data[0]}")
}
}
list.add(LightAppInternal(content))
}
private fun decodeCustomElem(
customElem: ImMsgBody.CustomElem,
list: MessageChainBuilder
) {
customElem.data.read {
kotlin.runCatching {
CustomMessage.load(this)
}.fold(
onFailure = {
if (it is CustomMessage.Companion.CustomMessageFullDataDeserializeInternalException) {
throw IllegalStateException(
"Internal error: " +
"exception while deserializing CustomMessage head data," +
" data=${customElem.data.toUHexString()}", it
)
} else {
it as CustomMessage.Companion.CustomMessageFullDataDeserializeUserException
throw IllegalStateException(
"User error: " +
"exception while deserializing CustomMessage body," +
" body=${it.body.toUHexString()}", it
)
}
},
onSuccess = {
if (it != null) {
list.add(it)
}
}
)
}
}
private fun decodeCommonElem(
commonElem: ImMsgBody.CommonElem,
list: MessageChainBuilder
) {
when (commonElem.serviceType) {
23 -> {
val proto =
commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype23.serializer())
list.add(VipFace(VipFace.Kind(proto.faceType, proto.faceSummary), proto.faceBubbleCount))
}
2 -> {
val proto =
commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype2.serializer())
list.add(PokeMessage(
proto.vaspokeName.takeIf { it.isNotEmpty() }
?: PokeMessage.values.firstOrNull { it.id == proto.vaspokeId && it.pokeType == proto.pokeType }?.name
.orEmpty(),
proto.pokeType,
proto.vaspokeId
)
)
}
3 -> {
val proto =
commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype3.serializer())
if (proto.flashTroopPic != null) {
list.add(FlashImage(OnlineGroupImageImpl(proto.flashTroopPic)))
}
if (proto.flashC2cPic != null) {
list.add(FlashImage(OnlineFriendImageImpl(proto.flashC2cPic)))
}
}
33 -> {
val proto =
commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype33.serializer())
list.add(Face(proto.index))
}
}
}
private fun decodeRichMessage(
richMsg: ImMsgBody.RichMsg,
builder: MessageChainBuilder
) {
val content = runWithBugReport("解析 richMsg", { richMsg.template1.toUHexString() }) {
when (richMsg.template1[0].toInt()) {
0 -> richMsg.template1.encodeToString(offset = 1)
1 -> richMsg.template1.unzip(1).encodeToString()
else -> error("unknown compression flag=${richMsg.template1[0]}")
}
}
when (richMsg.serviceId) {
// 5: 使用微博长图转换功能分享到QQ群
/*
<?xml version="1.0" encoding="utf-8"?><msg serviceID="5" templateID="12345" brief="[分享]想要沐浴阳光,就别钻进
阴影 ???" ><item layout="0"><image uuid="{E5F68BD5-05F8-148B-9DA7-FECD026D30AD}.jpg" md5="E5F68BD505F8148B9DA7FECD026D
30AD" GroupFiledid="2167263882" minWidth="120" minHeight="120" maxWidth="180" maxHeight="180" /></item><source name="
浪微博" icon="http://i.gtimg.cn/open/app_icon/00/73/69/03//100736903_100_m.png" appid="100736903" action="" i_actionData
="" a_actionData="" url=""/></msg>
*/
/**
* json?
*/
1 -> @Suppress("DEPRECATION_ERROR")
builder.add(SimpleServiceMessage(1, content))
/**
* [LongMessageInternal], [ForwardMessage]
*/
35 -> {
fun findStringProperty(name: String): String {
return content.substringAfter("$name=\"", "").substringBefore("\"", "")
}
val resId = findStringProperty("m_resid")
val msg = if (resId.isEmpty()) {
SimpleServiceMessage(35, content)
} else when (findStringProperty("multiMsgFlag").toIntOrNull()) {
1 -> LongMessageInternal(content, resId)
0 -> ForwardMessageInternal(content, resId)
else -> {
// from PC QQ
if (findStringProperty("action") == "viewMultiMsg") {
ForwardMessageInternal(content, resId)
} else {
SimpleServiceMessage(35, content)
}
}
}
builder.add(msg)
}
// 104 新群员入群的消息
else -> {
builder.add(SimpleServiceMessage(richMsg.serviceId, content))
}
}
}
fun ImMsgBody.Ptt.toVoice() = Voice(
kotlinx.io.core.String(fileName),
fileMd5,
fileSize.toLong(),
format,
kotlinx.io.core.String(downPara)
)
}
/**
* 解析 [ForwardMessageInternal], [LongMessageInternal]
* 并处理换行符问题
*/
internal suspend fun MessageChain.refine(contact: Contact): MessageChain {
val convertLineSeparator = contact.bot.asQQAndroidBot().configuration.convertLineSeparator
if (none {
it is RefinableMessage
|| (it is PlainText && convertLineSeparator && it.content.contains('\r'))
}
) return this
val builder = MessageChainBuilder(this.size)
for (singleMessage in this) {
if (singleMessage is RefinableMessage) {
val v = singleMessage.refine(contact, this)
if (v != null) builder.add(v)
} else if (singleMessage is PlainText && convertLineSeparator) {
val content = singleMessage.content
if (content.contains('\r')) {
builder.add(
PlainText(
content
.replace("\r\n", "\n")
.replace('\r', '\n')
)
)
} else {
builder.add(singleMessage)
}
} else {
builder.add(singleMessage)
}
}
return builder.build()
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.message
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
internal fun contextualBugReportException(
context: String,
forDebug: String,
e: Throwable? = null,
additional: String = ""
): IllegalStateException {
return IllegalStateException(
"$context 时遇到了意料之中的问题. 请完整复制此日志提交给 mirai: https://github.com/mamoe/mirai/issues/new $additional 调试信息: $forDebug",
e
)
}
@OptIn(ExperimentalContracts::class)
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
@kotlin.internal.InlineOnly
internal inline fun <R> runWithBugReport(context: String, forDebug: () -> String, block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
callsInPlace(forDebug, InvocationKind.AT_MOST_ONCE)
}
return runCatching(block).getOrElse {
throw contextualBugReportException(context, forDebug(), it)
}
}

View File

@ -1,673 +0,0 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:OptIn(LowLevelApi::class)
@file:Suppress("EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR")
package net.mamoe.mirai.internal.message
import kotlinx.io.core.String
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.Bot
import net.mamoe.mirai.LowLevelApi
import net.mamoe.mirai.contact.AnonymousMember
import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.internal.network.protocol.data.proto.*
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.utils.*
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.*
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。")
private val UNSUPPORTED_POKE_MESSAGE_PLAIN = PlainText("[戳一戳]请使用最新版手机QQ体验新功能。")
private val UNSUPPORTED_FLASH_MESSAGE_PLAIN = PlainText("[闪照]请使用新版手机QQ查看闪照。")
private val UNSUPPORTED_VOICE_MESSAGE_PLAIN = PlainText("收到语音消息你需要升级到最新版QQ才能接收升级地址https://im.qq.com")
@OptIn(ExperimentalStdlibApi::class)
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
internal fun MessageChain.toRichTextElems(
messageTarget: ContactOrBot?,
withGeneralFlags: Boolean
): MutableList<ImMsgBody.Elem> {
val forGroup = messageTarget is Group
val elements = ArrayList<ImMsgBody.Elem>(this.size)
if (this.anyIsInstance<QuoteReply>()) {
when (val source = this[QuoteReply]!!.source) {
is MessageSourceInternal -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData()))
else -> error("unsupported MessageSource implementation: ${source::class.simpleName}. Don't implement your own MessageSource.")
}
}
var longTextResId: String? = null
fun transformOneMessage(currentMessage: Message) {
if (currentMessage is RichMessage) {
val content = currentMessage.content.toByteArray().zip()
when (currentMessage) {
is ForwardMessageInternal -> {
elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = currentMessage.serviceId, // ok
template1 = byteArrayOf(1) + content
)
)
)
transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN)
}
is LongMessageInternal -> {
check(longTextResId == null) { "There must be no more than one LongMessage element in the message chain" }
elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = currentMessage.serviceId, // ok
template1 = byteArrayOf(1) + content
)
)
)
transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN)
longTextResId = currentMessage.resId
}
is LightApp -> elements.add(
ImMsgBody.Elem(
lightApp = ImMsgBody.LightAppElem(
data = byteArrayOf(1) + content
)
)
)
else -> elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = when (currentMessage) {
is ServiceMessage -> currentMessage.serviceId
else -> error("unsupported RichMessage: ${currentMessage::class.simpleName}")
},
template1 = byteArrayOf(1) + content
)
)
)
}
}
when (currentMessage) {
is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = currentMessage.content)))
is CustomMessage -> {
@Suppress("UNCHECKED_CAST")
elements.add(
ImMsgBody.Elem(
customElem = ImMsgBody.CustomElem(
enumType = MIRAI_CUSTOM_ELEM_TYPE,
data = CustomMessage.dump(
currentMessage.getFactory() as CustomMessage.Factory<CustomMessage>,
currentMessage
)
)
)
)
}
is At -> {
elements.add(ImMsgBody.Elem(text = currentMessage.toJceData(messageTarget.safeCast())))
// elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
// removed by https://github.com/mamoe/mirai/issues/524
// 发送 QuoteReply 消息时无可避免的产生多余空格 #524
}
is PokeMessage -> {
elements.add(
ImMsgBody.Elem(
commonElem = ImMsgBody.CommonElem(
serviceType = 2,
businessType = currentMessage.pokeType,
pbElem = HummerCommelem.MsgElemInfoServtype2(
pokeType = currentMessage.pokeType,
vaspokeId = currentMessage.id,
vaspokeMinver = "7.2.0",
vaspokeName = currentMessage.name
).toByteArray(HummerCommelem.MsgElemInfoServtype2.serializer())
)
)
)
transformOneMessage(UNSUPPORTED_POKE_MESSAGE_PLAIN)
}
is OfflineGroupImage -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.toJceData().toNotOnlineImage()))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.toJceData()))
}
}
is OnlineGroupImageImpl -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.delegate.toNotOnlineImage()))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.delegate))
}
}
is OnlineFriendImageImpl -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.delegate))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.delegate.toCustomFace()))
}
}
is OfflineFriendImage -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.toJceData()))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.toJceData().toCustomFace()))
}
}
is FlashImage -> elements.add(currentMessage.toJceData(messageTarget))
.also { transformOneMessage(UNSUPPORTED_FLASH_MESSAGE_PLAIN) }
is AtAll -> elements.add(atAllData)
is Face -> elements.add(
if (currentMessage.id >= 260) {
ImMsgBody.Elem(commonElem = currentMessage.toCommData())
} else {
ImMsgBody.Elem(face = currentMessage.toJceData())
}
)
is QuoteReply -> {
if (forGroup) {
when (val source = currentMessage.source) {
is OnlineMessageSource.Incoming.FromGroup -> {
val sender0 = source.sender
if (sender0 !is AnonymousMember)
transformOneMessage(At(sender0))
// transformOneMessage(PlainText(" "))
// removed by https://github.com/mamoe/mirai/issues/524
// 发送 QuoteReply 消息时无可避免的产生多余空格 #524
}
}
}
}
is MarketFace -> {
if (currentMessage is MarketFaceImpl) {
elements.add(ImMsgBody.Elem(marketFace = currentMessage.delegate))
}
//兼容信息
transformOneMessage(PlainText(currentMessage.name))
if (currentMessage is MarketFaceImpl) {
elements.add(
ImMsgBody.Elem(
extraInfo = ImMsgBody.ExtraInfo(flags = 8, groupMask = 1)
)
)
}
}
is VipFace -> transformOneMessage(PlainText(currentMessage.contentToString()))
is PttMessage -> {
elements.add(
ImMsgBody.Elem(
extraInfo = ImMsgBody.ExtraInfo(flags = 16, groupMask = 1)
)
)
elements.add(
ImMsgBody.Elem(
elemFlags2 = ImMsgBody.ElemFlags2(
vipStatus = 1
)
)
)
}
is MusicShare -> {
// 只有在 QuoteReply 的 source 里才会进行 MusicShare 转换, 因此可以转 PT.
// 发送消息时会被特殊处理
transformOneMessage(PlainText(currentMessage.content))
}
is ForwardMessage,
is MessageSource, // mirai metadata only
is RichMessage // already transformed above
-> {
}
is InternalFlagOnlyMessage, is ShowImageFlag -> {
// ignore
}
else -> error("unsupported message type: ${currentMessage::class.simpleName}")
}
}
this.forEach(::transformOneMessage)
if (withGeneralFlags) {
when {
longTextResId != null -> {
elements.add(
ImMsgBody.Elem(
generalFlags = ImMsgBody.GeneralFlags(
longTextFlag = 1,
longTextResid = longTextResId!!,
pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()
)
)
)
}
this.anyIsInstance<MarketFaceImpl>() -> {
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_MARKET_FACE)))
}
this.anyIsInstance<RichMessage>() -> {
// 08 09 78 00 A0 01 81 DC 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_RICH_MESSAGE)))
}
this.anyIsInstance<FlashImage>() -> {
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_DOUTU)))
}
this.anyIsInstance<PttMessage>() -> {
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_PTT)))
}
else -> elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_ELSE)))
}
}
return elements
}
private val PB_RESERVE_FOR_RICH_MESSAGE =
"08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes()
private val PB_RESERVE_FOR_PTT =
"78 00 F8 01 00 C8 02 00 AA 03 26 08 22 12 22 41 20 41 3B 25 3E 16 45 3F 43 2F 29 3E 44 24 14 18 46 3D 2B 4A 44 3A 18 2E 19 29 1B 26 32 31 31 29 43".hexToBytes()
@Suppress("SpellCheckingInspection")
private val PB_RESERVE_FOR_DOUTU = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes()
private val PB_RESERVE_FOR_MARKET_FACE =
"02 78 80 80 04 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 00 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 10 3B 90 04 80 C0 80 80 04 B8 04 00 C0 04 00 CA 04 00 F8 04 80 80 04 88 05 00".hexToBytes()
private val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes()
internal fun MsgComm.Msg.toMessageChain(
bot: Bot,
groupIdOrZero: Long,
onlineSource: Boolean,
messageSourceKind: MessageSourceKind
): MessageChain = listOf(this).toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, messageSourceKind)
internal fun List<MsgOnlinePush.PbPushMsg>.toMessageChain(
bot: Bot,
groupIdOrZero: Long,
onlineSource: Boolean,
messageSourceKind: MessageSourceKind
): MessageChain = map { it.msg }.toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, messageSourceKind)
internal fun List<MsgComm.Msg>.toMessageChain(
bot: Bot?,
botId: Long,
groupIdOrZero: Long,
onlineSource: Boolean?,
messageSourceKind: MessageSourceKind
): MessageChain {
val elements = this.flatMap { it.msgBody.richText.elems }
@OptIn(ExperimentalStdlibApi::class)
val ptts = buildList<Message> {
this@toMessageChain.forEach { msg ->
msg.msgBody.richText.ptt?.run {
// when (fileType) {
// 4 -> Voice(String(fileName), fileMd5, fileSize.toLong(),String(downPara))
// else -> null
// }
add(Voice(String(fileName), fileMd5, fileSize.toLong(), format, String(downPara)))
}
}
}
return buildMessageChain(elements.size + 1 + ptts.size) {
when (onlineSource) {
true -> {
checkNotNull(bot) { "bot is null" }
when (messageSourceKind) {
MessageSourceKind.TEMP -> +OnlineMessageSourceFromTempImpl(bot, this@toMessageChain)
MessageSourceKind.GROUP -> +OnlineMessageSourceFromGroupImpl(bot, this@toMessageChain)
MessageSourceKind.FRIEND -> +OnlineMessageSourceFromFriendImpl(bot, this@toMessageChain)
MessageSourceKind.STRANGER -> +OnlineMessageSourceFromStrangerImpl(bot, this@toMessageChain)
}
}
false -> {
+OfflineMessageSourceImplData(bot, this@toMessageChain, botId)
}
null -> {
}
}
elements.joinToMessageChain(groupIdOrZero, messageSourceKind, botId, this)
addAll(ptts)
}.cleanupRubbishMessageElements()
}
// These two functions have difference method signature, don't combine.
internal fun ImMsgBody.SourceMsg.toMessageChain(
botId: Long,
messageSourceKind: MessageSourceKind,
groupIdOrZero: Long
): MessageChain {
val elements = this.elems
if (elements.isEmpty())
error("elements for SourceMsg is empty")
return buildMessageChain(elements.size + 1) {
/*
+OfflineMessageSourceImplData(
delegate = this@toMessageChain,
botId = botId,
messageSourceKind = messageSourceKind,
groupIdOrZero = groupIdOrZero
)*/
elements.joinToMessageChain(groupIdOrZero, messageSourceKind, botId, this)
}.cleanupRubbishMessageElements()
}
private fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
var previousLast: SingleMessage? = null
var last: SingleMessage? = null
return buildMessageChain(initialSize = this.count()) {
this@cleanupRubbishMessageElements.forEach { element ->
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
if (last is LongMessageInternal && element is PlainText) {
if (element == UNSUPPORTED_MERGED_MESSAGE_PLAIN) {
previousLast = last
last = element
return@forEach
}
}
if (last is PokeMessage && element is PlainText) {
if (element == UNSUPPORTED_POKE_MESSAGE_PLAIN) {
previousLast = last
last = element
return@forEach
}
}
if (last is VipFace && element is PlainText) {
val l = last as VipFace
if (element.content.length == 4 + (l.count / 10) + l.kind.name.length) {
previousLast = last
last = element
return@forEach
}
}
// 解决tim发送的语音无法正常识别
if (element is PlainText) {
if (element == UNSUPPORTED_VOICE_MESSAGE_PLAIN) {
previousLast = last
last = element
return@forEach
}
}
if (element is PlainText && last is At && previousLast is QuoteReply
&& element.content.startsWith(' ')
) {
// Android QQ 发送, 是 Quote+At+PlainText(" xxx") // 首空格
removeLastOrNull() // At
val new = PlainText(element.content.substring(1))
add(new)
previousLast = null
last = new
return@forEach
}
if (element is QuoteReply) {
// 客户端为兼容早期不支持 QuoteReply 的客户端而添加的 At
removeLastOrNull()?.let { rm ->
if ((rm as? PlainText)?.content != " ") add(rm)
else removeLastOrNull()?.let { rm2 ->
if (rm2 !is At) add(rm2)
}
}
}
if (element is PlainText) { // 处理分片消息
append(element.content)
} else {
add(element)
}
previousLast = last
last = element
}
}
}
internal inline fun <reified R> Iterable<*>.firstIsInstanceOrNull(): R? {
for (it in this) {
if (it is R) {
return it
}
}
return null
}
internal val MIRAI_CUSTOM_ELEM_TYPE = "mirai".hashCode() // 103904510
internal fun List<ImMsgBody.Elem>.joinToMessageChain(
groupIdOrZero: Long,
messageSourceKind: MessageSourceKind,
botId: Long,
list: MessageChainBuilder
) {
// (this._miraiContentToString().soutv())
var marketFace: MarketFaceImpl? = null
this.forEach { element ->
when {
element.srcMsg != null -> {
list.add(
QuoteReply(
OfflineMessageSourceImplData(
element.srcMsg,
botId,
messageSourceKind,
groupIdOrZero
)
)
)
}
element.notOnlineImage != null -> list.add(OnlineFriendImageImpl(element.notOnlineImage))
element.customFace != null -> {
list.add(OnlineGroupImageImpl(element.customFace))
element.customFace.pbReserve.let {
if (it.isNotEmpty() && it.loadAs(CustomFace.ResvAttr.serializer()).msgImageShow != null) {
list.add(ShowImageFlag)
}
}
}
element.face != null -> list.add(Face(element.face.index))
element.text != null -> {
if (element.text.attr6Buf.isEmpty()) {
if (marketFace != null && marketFace!!.name.isEmpty()) {
marketFace!!.delegate.faceName = element.text.str.toByteArray()
} else {
list.add(PlainText(element.text.str))
}
} else {
val id: Long
element.text.attr6Buf.read {
discardExact(7)
id = readUInt().toLong()
}
if (id == 0L) {
list.add(AtAll)
} else {
list.add(At(id)) // element.text.str
}
}
}
element.marketFace != null -> {
list.add(MarketFaceImpl(element.marketFace).also {
marketFace = it
})
}
element.lightApp != null -> {
val content = runWithBugReport("解析 lightApp",
{ "resId=" + element.lightApp.msgResid + "data=" + element.lightApp.data.toUHexString() }) {
when (element.lightApp.data[0].toInt()) {
0 -> element.lightApp.data.encodeToString(offset = 1)
1 -> element.lightApp.data.unzip(1).encodeToString()
else -> error("unknown compression flag=${element.lightApp.data[0]}")
}
}
list.add(LightApp(content).refine())
}
element.richMsg != null -> {
val content = runWithBugReport("解析 richMsg", { element.richMsg.template1.toUHexString() }) {
when (element.richMsg.template1[0].toInt()) {
0 -> element.richMsg.template1.encodeToString(offset = 1)
1 -> element.richMsg.template1.unzip(1).encodeToString()
else -> error("unknown compression flag=${element.richMsg.template1[0]}")
}
}
when (element.richMsg.serviceId) {
// 5: 使用微博长图转换功能分享到QQ群
/*
<?xml version="1.0" encoding="utf-8"?><msg serviceID="5" templateID="12345" brief="[分享]想要沐浴阳光,就别钻进
阴影 ???" ><item layout="0"><image uuid="{E5F68BD5-05F8-148B-9DA7-FECD026D30AD}.jpg" md5="E5F68BD505F8148B9DA7FECD026D
30AD" GroupFiledid="2167263882" minWidth="120" minHeight="120" maxWidth="180" maxHeight="180" /></item><source name="
浪微博" icon="http://i.gtimg.cn/open/app_icon/00/73/69/03//100736903_100_m.png" appid="100736903" action="" i_actionData
="" a_actionData="" url=""/></msg>
*/
/**
* json?
*/
1 -> @Suppress("DEPRECATION_ERROR")
list.add(SimpleServiceMessage(1, content))
/**
* [LongMessageInternal], [ForwardMessage]
*/
35 -> {
val resId = this.firstIsInstanceOrNull<ImMsgBody.GeneralFlags>()?.longTextResid
if (resId != null) {
// TODO: 2020/4/29 解析长消息
list.add(SimpleServiceMessage(35, content)) // resId
} else {
// TODO: 2020/4/29 解析合并转发
list.add(SimpleServiceMessage(35, content))
}
}
// 104 新群员入群的消息
else -> {
list.add(SimpleServiceMessage(element.richMsg.serviceId, content))
}
}
}
element.elemFlags2 != null
|| element.extraInfo != null
|| element.generalFlags != null -> {
}
element.customElem != null -> {
element.customElem.data.read {
kotlin.runCatching {
CustomMessage.load(this)
}.fold(
onFailure = {
if (it is CustomMessage.Companion.CustomMessageFullDataDeserializeInternalException) {
throw IllegalStateException(
"Internal error: " +
"exception while deserializing CustomMessage head data," +
" data=${element.customElem.data.toUHexString()}", it
)
} else {
it as CustomMessage.Companion.CustomMessageFullDataDeserializeUserException
throw IllegalStateException(
"User error: " +
"exception while deserializing CustomMessage body," +
" body=${it.body.toUHexString()}", it
)
}
},
onSuccess = {
if (it != null) {
list.add(it)
}
}
)
}
}
element.commonElem != null -> {
when (element.commonElem.serviceType) {
23 -> {
val proto = element.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype23.serializer())
list.add(VipFace(VipFace.Kind(proto.faceType, proto.faceSummary), proto.faceBubbleCount))
}
2 -> {
val proto = element.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype2.serializer())
list.add(PokeMessage(
proto.vaspokeName.takeIf { it.isNotEmpty() }
?: PokeMessage.values.firstOrNull { it.id == proto.vaspokeId && it.pokeType == proto.pokeType }?.name
.orEmpty(),
proto.pokeType,
proto.vaspokeId
)
)
}
3 -> {
val proto = element.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype3.serializer())
if (proto.flashTroopPic != null) {
list.add(FlashImage(OnlineGroupImageImpl(proto.flashTroopPic)))
}
if (proto.flashC2cPic != null) {
list.add(FlashImage(OnlineFriendImageImpl(proto.flashC2cPic)))
}
}
33 -> {
val proto = element.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype33.serializer())
list.add(Face(proto.index))
}
}
}
else -> {
// println(it._miraiContentToString())
}
}
}
}
internal fun contextualBugReportException(
context: String,
forDebug: String,
e: Throwable? = null,
additional: String = ""
): IllegalStateException {
return IllegalStateException("$context 时遇到了意料之中的问题. 请完整复制此日志提交给 mirai: https://github.com/mamoe/mirai/issues/new $additional 调试信息: $forDebug", e)
}
@OptIn(ExperimentalContracts::class)
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
@kotlin.internal.InlineOnly
internal inline fun <R> runWithBugReport(context: String, forDebug: () -> String, block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
callsInPlace(forDebug, InvocationKind.AT_MOST_ONCE)
}
return runCatching(block).getOrElse {
throw contextualBugReportException(context, forDebug(), it)
}
}

View File

@ -10,14 +10,10 @@
package net.mamoe.mirai.internal.message
import kotlinx.io.core.toByteArray
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.Face
import net.mamoe.mirai.message.data.MarketFace
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toByteArray
@ -42,18 +38,4 @@ internal fun Face.toCommData(): ImMsgBody.CommonElem {
businessType = 1
)
}
@SerialName(MarketFace.SERIAL_NAME)
@Serializable
internal data class MarketFaceImpl internal constructor(
internal val delegate: ImMsgBody.MarketFace,
) : MarketFace {
override val name: String get() = delegate.faceName.decodeToString()
@Transient
override val id: Int = delegate.tabId
override fun toString() = "[mirai:marketface:$id,$name]"
}

View File

@ -17,7 +17,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.Stranger
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.contact.checkIsGroupImpl
import net.mamoe.mirai.internal.contact.newAnonymous
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
@ -25,66 +25,32 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.SourceMsg
import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.utils.encodeToBase64
import net.mamoe.mirai.utils.encodeToString
import net.mamoe.mirai.utils.mapToIntArray
import java.util.concurrent.atomic.AtomicBoolean
internal interface MessageSourceInternal {
@Transient
val sequenceIds: IntArray // ids
@Transient
val internalIds: IntArray // randomId
@Deprecated("don't use this internally. Use sequenceId or random instead.", level = DeprecationLevel.ERROR)
@Transient
val ids: IntArray
@Transient
val isRecalledOrPlanned: AtomicBoolean
fun toJceData(): ImMsgBody.SourceMsg
}
@Suppress("RedundantSuspendModifier", "unused")
internal suspend fun MessageSource.ensureSequenceIdAvailable() {
if (this is OnlineMessageSourceToGroupImpl) {
ensureSequenceIdAvailable()
}
}
@Suppress("RedundantSuspendModifier", "unused")
internal suspend inline fun Message.ensureSequenceIdAvailable() {
(this as? MessageChain)?.sourceOrNull?.ensureSequenceIdAvailable()
}
@Serializable(OnlineMessageSourceFromFriendImpl.Serializer::class)
internal class OnlineMessageSourceFromFriendImpl(
override val bot: Bot,
val msg: List<MsgComm.Msg>
msg: List<MsgComm.Msg>
) : OnlineMessageSource.Incoming.FromFriend(), MessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceFromFriend")
override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq }
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val ids: IntArray get() = sequenceIds// msg.msgBody.richText.attr!!.random
override val internalIds: IntArray
get() = msg.mapToIntArray {
it.msgBody.richText.attr?.random ?: 0
} // other client 消息的这个是0
override val time: Int get() = msg.first().msgHead.msgTime
override val internalIds: IntArray = msg.mapToIntArray {
it.msgBody.richText.attr?.random ?: 0
} // other client 消息的这个是0
override val time: Int = msg.first().msgHead.msgTime
override val originalMessage: MessageChain by lazy {
msg.toMessageChain(
bot,
bot.id,
0,
null,
MessageSourceKind.FRIEND
)
msg.toMessageChainNoSource(bot.id, 0, MessageSourceKind.FRIEND)
}
override val sender: Friend get() = bot.getFriendOrFail(msg.first().msgHead.fromUin)
override val sender: Friend = bot.getFriendOrFail(msg.first().msgHead.fromUin)
private val jceData by lazy { msg.toJceDataPrivate(internalIds) }
@ -94,28 +60,21 @@ internal class OnlineMessageSourceFromFriendImpl(
@Serializable(OnlineMessageSourceFromStrangerImpl.Serializer::class)
internal class OnlineMessageSourceFromStrangerImpl(
override val bot: Bot,
val msg: List<MsgComm.Msg>
msg: List<MsgComm.Msg>
) : OnlineMessageSource.Incoming.FromStranger(), MessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceFromStranger")
override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq }
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val ids: IntArray get() = sequenceIds// msg.msgBody.richText.attr!!.random
override val internalIds: IntArray
get() = msg.mapToIntArray {
it.msgBody.richText.attr?.random ?: 0
} // other client 消息的这个是0
override val time: Int get() = msg.first().msgHead.msgTime
override val internalIds: IntArray = msg.mapToIntArray {
it.msgBody.richText.attr?.random ?: 0
} // other client 消息的这个是0
override val time: Int = msg.first().msgHead.msgTime
override val originalMessage: MessageChain by lazy {
msg.toMessageChain(
bot,
bot.id,
0,
null,
MessageSourceKind.STRANGER
)
msg.toMessageChainNoSource(bot.id, 0, MessageSourceKind.STRANGER)
}
override val sender: Stranger get() = bot.getStrangerOrFail(msg.first().msgHead.fromUin)
override val sender: Stranger = bot.getStrangerOrFail(msg.first().msgHead.fromUin)
private val jceData by lazy { msg.toJceDataPrivate(internalIds) }
@ -126,100 +85,101 @@ private fun List<MsgComm.Msg>.toJceDataPrivate(ids: IntArray): ImMsgBody.SourceM
val elements = flatMap { it.msgBody.richText.elems }.toMutableList().also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
return ImMsgBody.SourceMsg(
origSeqs = mapToIntArray { it.msgHead.msgSeq },
senderUin = first().msgHead.fromUin,
toUin = first().msgHead.toUin,
flag = 1,
elems = flatMap { it.msgBody.richText.elems },
type = 0,
time = this.first().msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = ids.map { it.toLong() and 0xFFFF_FFFF }
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = this.first().msgHead.fromUin, // qq
toUin = this.first().msgHead.toUin, // group
msgType = this.first().msgHead.msgType, // 82?
c2cCmd = this.first().msgHead.c2cCmd,
msgSeq = this.first().msgHead.msgSeq,
msgTime = this.first().msgHead.msgTime,
msgUid = ids.single().toLong() and 0xFFFF_FFFF, // ok
// groupInfo = MsgComm.GroupInfo(groupCode = this.msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = elements
first().msgHead.run {
return ImMsgBody.SourceMsg(
origSeqs = mapToIntArray { it.msgHead.msgSeq },
senderUin = fromUin,
toUin = toUin,
flag = 1,
elems = flatMap { it.msgBody.richText.elems },
type = 0,
time = msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = ids.map { it.toLong() and 0xFFFF_FFFF }
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = fromUin, // qq
toUin = toUin, // group
msgType = msgType, // 82?
c2cCmd = c2cCmd,
msgSeq = msgSeq,
msgTime = msgTime,
msgUid = ids.single().toLong() and 0xFFFF_FFFF, // ok
// groupInfo = MsgComm.GroupInfo(groupCode = msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = elements
)
)
)
).toByteArray(MsgComm.Msg.serializer())
)
).toByteArray(MsgComm.Msg.serializer())
)
}
}
@Serializable(OnlineMessageSourceFromTempImpl.Serializer::class)
internal class OnlineMessageSourceFromTempImpl(
override val bot: Bot,
private val msg: List<MsgComm.Msg>
msg: List<MsgComm.Msg>
) : OnlineMessageSource.Incoming.FromTemp(), MessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceFromTemp")
override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
override val internalIds: IntArray get() = msg.mapToIntArray { it.msgBody.richText.attr!!.random }
override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq }
override val internalIds: IntArray = msg.mapToIntArray { it.msgBody.richText.attr!!.random }
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val ids: IntArray get() = sequenceIds//
override val time: Int get() = msg.first().msgHead.msgTime
override val time: Int = msg.first().msgHead.msgTime
override val originalMessage: MessageChain by lazy {
msg.toMessageChain(
bot,
bot.id,
groupIdOrZero = 0,
onlineSource = null,
MessageSourceKind.TEMP
)
msg.toMessageChainNoSource(bot.id, groupIdOrZero = 0, MessageSourceKind.TEMP)
}
override val sender: Member = with(msg.first().msgHead) {
bot.getGroupOrFail(c2cTmpMsgHead!!.groupUin).getOrFail(fromUin)
}
override val sender: Member
get() = with(msg.first().msgHead) {
bot.getGroupOrFail(c2cTmpMsgHead!!.groupUin).getOrFail(fromUin)
}
private val jceData by lazy { msg.toJceDataPrivate(internalIds) }
override fun toJceData(): ImMsgBody.SourceMsg = jceData
}
@Serializable(OnlineMessageSourceFromGroupImpl.Serializer::class)
internal data class OnlineMessageSourceFromGroupImpl(
internal class OnlineMessageSourceFromGroupImpl(
override val bot: Bot,
private val msg: List<MsgComm.Msg>
msg: List<MsgComm.Msg>
) : OnlineMessageSource.Incoming.FromGroup(), MessageSourceInternal {
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceFromGroupImpl")
@Transient
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
override val internalIds: IntArray get() = msg.mapToIntArray { it.msgBody.richText.attr!!.random }
override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq }
override val internalIds: IntArray = msg.mapToIntArray { it.msgBody.richText.attr!!.random }
override val ids: IntArray get() = sequenceIds
override val time: Int get() = msg.first().msgHead.msgTime
override val time: Int = msg.first().msgHead.msgTime
override val originalMessage: MessageChain by lazy {
msg.toMessageChain(bot, bot.id, groupIdOrZero = group.id, onlineSource = null, MessageSourceKind.GROUP)
msg.toMessageChainNoSource(bot.id, groupIdOrZero = group.id, MessageSourceKind.GROUP)
}
override val sender: Member by lazy {
(bot.getGroup(
msg.first().msgHead.groupInfo?.groupCode
?: error("cannot find groupCode for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
) as GroupImpl).run {
get(msg.first().msgHead.fromUin)
?: msg.first().msgBody.richText.elems.firstOrNull { it.anonGroupMsg != null }?.run {
newAnonymous(anonGroupMsg!!.anonNick.encodeToString(), anonGroupMsg.anonId.encodeToBase64())
}
?: error("cannot find member for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
val groupCode = msg.first().msgHead.groupInfo?.groupCode
?: error("cannot find groupCode for OnlineMessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
val group = bot.getGroup(groupCode)?.checkIsGroupImpl()
?: error("cannot find group for OnlineMessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
val member = group[msg.first().msgHead.fromUin]
if (member != null) return@lazy member
val anonymousInfo = msg.first().msgBody.richText.elems.firstOrNull { it.anonGroupMsg != null }
?: error("cannot find member for OnlineMessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
anonymousInfo.run {
group.newAnonymous(anonGroupMsg!!.anonNick.encodeToString(), anonGroupMsg.anonId.encodeToBase64())
}
}
override fun toJceData(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
private val jceData by lazy {
ImMsgBody.SourceMsg(
origSeqs = intArrayOf(msg.first().msgHead.msgSeq),
senderUin = msg.first().msgHead.fromUin,
toUin = 0,
@ -231,4 +191,8 @@ internal data class OnlineMessageSourceFromGroupImpl(
srcMsg = EMPTY_BYTE_ARRAY
)
}
}
override fun toJceData(): ImMsgBody.SourceMsg {
return jceData
}
}

View File

@ -12,42 +12,48 @@ package net.mamoe.mirai.internal.message
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import net.mamoe.mirai.message.data.LightApp
import net.mamoe.mirai.message.data.MusicKind
import net.mamoe.mirai.message.data.MusicShare
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.safeCast
private val json = Json {
ignoreUnknownKeys = true
}
internal data class LightAppInternal(
override val content: String
) : RichMessage, RefinableMessage {
companion object Key :
AbstractPolymorphicMessageKey<RichMessage, LightAppInternal>(RichMessage, { it.safeCast() })
internal fun LightApp.tryDeserialize(): LightAppStruct? {
return kotlin.runCatching {
json.decodeFromString(LightAppStruct.serializer(), this.content)
}.getOrNull()
}
/**
* 识别 app 内容, 如果有必要
*/
internal fun LightApp.refine(): SingleMessage {
val struct = tryDeserialize() ?: return this
struct.run {
if (meta.music != null) {
MusicKind.values().find { it.appId.toInt() == meta.music.appid }?.let { musicType ->
meta.music.run {
return MusicShare(
kind = musicType, title = title, summary = desc,
jumpUrl = jumpUrl, pictureUrl = preview, musicUrl = musicUrl, brief = prompt
)
override suspend fun refine(contact: Contact, context: MessageChain): Message {
val struct = tryDeserialize() ?: return LightApp(content)
struct.run {
if (meta.music != null) {
MusicKind.values().find { it.appId.toInt() == meta.music.appid }?.let { musicType ->
meta.music.run {
return RichMessageOrigin(
LightApp(content),
null,
RichMessageKind.MUSIC_SHARE
) + MusicShare(
kind = musicType, title = title, summary = desc,
jumpUrl = jumpUrl, pictureUrl = preview, musicUrl = musicUrl, brief = prompt
)
}
}
}
}
return LightApp(content)
}
}
return this
private val json = Json {
ignoreUnknownKeys = true
isLenient = true
}
internal fun LightAppInternal.tryDeserialize(): LightAppStruct? {
return kotlin.runCatching {
json.decodeFromString(LightAppStruct.serializer(), this.content)
}.getOrNull()
}
/*

View File

@ -0,0 +1,292 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.message
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.contact.AnonymousMember
import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.safeCast
import net.mamoe.mirai.utils.zip
internal val MIRAI_CUSTOM_ELEM_TYPE = "mirai".hashCode() // 103904510
internal val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。")
internal val UNSUPPORTED_POKE_MESSAGE_PLAIN = PlainText("[戳一戳]请使用最新版手机QQ体验新功能。")
internal val UNSUPPORTED_FLASH_MESSAGE_PLAIN = PlainText("[闪照]请使用新版手机QQ查看闪照。")
internal val UNSUPPORTED_VOICE_MESSAGE_PLAIN = PlainText("收到语音消息你需要升级到最新版QQ才能接收升级地址https://im.qq.com")
@OptIn(ExperimentalStdlibApi::class)
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
internal fun MessageChain.toRichTextElems(
messageTarget: ContactOrBot?,
withGeneralFlags: Boolean
): MutableList<ImMsgBody.Elem> {
val forGroup = messageTarget is Group
val elements = ArrayList<ImMsgBody.Elem>(this.size)
if (this.anyIsInstance<QuoteReply>()) {
when (val source = this[QuoteReply]!!.source) {
is MessageSourceInternal -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData()))
else -> error("unsupported MessageSource implementation: ${source::class.simpleName}. Don't implement your own MessageSource.")
}
}
var longTextResId: String? = null
fun transformOneMessage(currentMessage: Message) {
if (currentMessage is RichMessage) {
val content = currentMessage.content.toByteArray().zip()
when (currentMessage) {
is ForwardMessageInternal -> {
elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = currentMessage.serviceId, // ok
template1 = byteArrayOf(1) + content
)
)
)
transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN)
}
is LongMessageInternal -> {
check(longTextResId == null) { "There must be no more than one LongMessage element in the message chain" }
elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = currentMessage.serviceId, // ok
template1 = byteArrayOf(1) + content
)
)
)
transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN)
longTextResId = currentMessage.resId
}
is LightApp -> elements.add(
ImMsgBody.Elem(
lightApp = ImMsgBody.LightAppElem(
data = byteArrayOf(1) + content
)
)
)
else -> elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = when (currentMessage) {
is ServiceMessage -> currentMessage.serviceId
else -> error("unsupported RichMessage: ${currentMessage::class.simpleName}")
},
template1 = byteArrayOf(1) + content
)
)
)
}
}
when (currentMessage) {
is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = currentMessage.content)))
is CustomMessage -> {
@Suppress("UNCHECKED_CAST")
elements.add(
ImMsgBody.Elem(
customElem = ImMsgBody.CustomElem(
enumType = MIRAI_CUSTOM_ELEM_TYPE,
data = CustomMessage.dump(
currentMessage.getFactory() as CustomMessage.Factory<CustomMessage>,
currentMessage
)
)
)
)
}
is At -> {
elements.add(ImMsgBody.Elem(text = currentMessage.toJceData(messageTarget.safeCast())))
// elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
// removed by https://github.com/mamoe/mirai/issues/524
// 发送 QuoteReply 消息时无可避免的产生多余空格 #524
}
is PokeMessage -> {
elements.add(
ImMsgBody.Elem(
commonElem = ImMsgBody.CommonElem(
serviceType = 2,
businessType = currentMessage.pokeType,
pbElem = HummerCommelem.MsgElemInfoServtype2(
pokeType = currentMessage.pokeType,
vaspokeId = currentMessage.id,
vaspokeMinver = "7.2.0",
vaspokeName = currentMessage.name
).toByteArray(HummerCommelem.MsgElemInfoServtype2.serializer())
)
)
)
transformOneMessage(UNSUPPORTED_POKE_MESSAGE_PLAIN)
}
is OfflineGroupImage -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.toJceData().toNotOnlineImage()))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.toJceData()))
}
}
is OnlineGroupImageImpl -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.delegate.toNotOnlineImage()))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.delegate))
}
}
is OnlineFriendImageImpl -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.delegate))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.delegate.toCustomFace()))
}
}
is OfflineFriendImage -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.toJceData()))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.toJceData().toCustomFace()))
}
}
is FlashImage -> elements.add(currentMessage.toJceData(messageTarget))
.also { transformOneMessage(UNSUPPORTED_FLASH_MESSAGE_PLAIN) }
is AtAll -> elements.add(atAllData)
is Face -> elements.add(
if (currentMessage.id >= 260) {
ImMsgBody.Elem(commonElem = currentMessage.toCommData())
} else {
ImMsgBody.Elem(face = currentMessage.toJceData())
}
)
is QuoteReply -> {
if (forGroup) {
when (val source = currentMessage.source) {
is OnlineMessageSource.Incoming.FromGroup -> {
val sender0 = source.sender
if (sender0 !is AnonymousMember)
transformOneMessage(At(sender0))
// transformOneMessage(PlainText(" "))
// removed by https://github.com/mamoe/mirai/issues/524
// 发送 QuoteReply 消息时无可避免的产生多余空格 #524
}
}
}
}
is MarketFace -> {
if (currentMessage is MarketFaceImpl) {
elements.add(ImMsgBody.Elem(marketFace = currentMessage.delegate))
}
//兼容信息
transformOneMessage(PlainText(currentMessage.name))
if (currentMessage is MarketFaceImpl) {
elements.add(
ImMsgBody.Elem(
extraInfo = ImMsgBody.ExtraInfo(flags = 8, groupMask = 1)
)
)
}
}
is VipFace -> transformOneMessage(PlainText(currentMessage.contentToString()))
is PttMessage -> {
elements.add(
ImMsgBody.Elem(
extraInfo = ImMsgBody.ExtraInfo(flags = 16, groupMask = 1)
)
)
elements.add(
ImMsgBody.Elem(
elemFlags2 = ImMsgBody.ElemFlags2(
vipStatus = 1
)
)
)
}
is MusicShare -> {
// 只有在 QuoteReply 的 source 里才会进行 MusicShare 转换, 因此可以转 PT.
// 发送消息时会被特殊处理
transformOneMessage(PlainText(currentMessage.content))
}
is ForwardMessage,
is MessageSource, // mirai metadata only
is RichMessage // already transformed above
-> {
}
is InternalFlagOnlyMessage, is ShowImageFlag -> {
// ignore
}
else -> {
// unrecognized types are ignored
// error("unsupported message type: ${currentMessage::class.simpleName}")
}
}
}
this.forEach(::transformOneMessage)
if (withGeneralFlags) {
when {
longTextResId != null -> {
elements.add(
ImMsgBody.Elem(
generalFlags = ImMsgBody.GeneralFlags(
longTextFlag = 1,
longTextResid = longTextResId!!,
pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()
)
)
)
}
this.anyIsInstance<MarketFaceImpl>() -> {
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_MARKET_FACE)))
}
this.anyIsInstance<RichMessage>() -> {
// 08 09 78 00 A0 01 81 DC 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_RICH_MESSAGE)))
}
this.anyIsInstance<FlashImage>() -> {
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_DOUTU)))
}
this.anyIsInstance<PttMessage>() -> {
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_PTT)))
}
else -> elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_ELSE)))
}
}
return elements
}
internal val PB_RESERVE_FOR_RICH_MESSAGE =
"08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes()
internal val PB_RESERVE_FOR_PTT =
"78 00 F8 01 00 C8 02 00 AA 03 26 08 22 12 22 41 20 41 3B 25 3E 16 45 3F 43 2F 29 3E 44 24 14 18 46 3D 2B 4A 44 3A 18 2E 19 29 1B 26 32 31 31 29 43".hexToBytes()
@Suppress("SpellCheckingInspection")
internal val PB_RESERVE_FOR_DOUTU = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes()
internal val PB_RESERVE_FOR_MARKET_FACE =
"02 78 80 80 04 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 00 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 10 3B 90 04 80 C0 80 80 04 B8 04 00 C0 04 00 CA 04 00 F8 04 80 80 04 88 05 00".hexToBytes()
internal val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes()

View File

@ -106,40 +106,24 @@ internal class OfflineMessageSourceImplData(
}
internal fun OfflineMessageSourceImplData(
bot: Bot?,
bot: Bot,
delegate: List<MsgComm.Msg>,
botId: Long,
kind: MessageSourceKind
): OfflineMessageSourceImplData {
val head = delegate.first().msgHead
val kind = when {
head.groupInfo != null -> {
MessageSourceKind.GROUP
}
head.c2cTmpMsgHead != null -> {
MessageSourceKind.TEMP
}
bot?.getStranger(head.fromUin) != null -> {
MessageSourceKind.STRANGER
}
else -> {
MessageSourceKind.FRIEND
}
}
return OfflineMessageSourceImplData(
kind = kind,
time = head.msgTime,
fromId = head.fromUin,
targetId = head.groupInfo?.groupCode ?: head.toUin,
originalMessage = delegate.toMessageChain(
null,
botId,
originalMessage = delegate.toMessageChainNoSource(
bot.id,
groupIdOrZero = head.groupInfo?.groupCode ?: 0,
onlineSource = null,
messageSourceKind = kind
),
ids = delegate.mapToIntArray { it.msgHead.msgSeq },
internalIds = delegate.mapToIntArray { it.msgHead.msgUid.toInt() },
botId = botId
botId = bot.id
).apply {
originElems = delegate.flatMap { it.msgBody.richText.elems }
}
@ -177,7 +161,7 @@ internal fun OfflineMessageSourceImplData(
internalIds = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer())
.origUids?.mapToIntArray { it.toInt() } ?: intArrayOf(),
time = delegate.time,
originalMessageLazy = lazy { delegate.toMessageChain(botId, messageSourceKind, groupIdOrZero) },
originalMessageLazy = lazy { delegate.toMessageChainNoSource(botId, messageSourceKind, groupIdOrZero) },
fromId = delegate.senderUin,
targetId = when {
groupIdOrZero != 0L -> groupIdOrZero

View File

@ -18,6 +18,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.Bot
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.WeakRefProperty
@ -46,7 +47,7 @@ internal abstract class BotNetworkHandler : CoroutineScope {
* 所属 [Bot]. 为弱引用
*/
@WeakRefProperty
abstract val bot: Bot
abstract val bot: QQAndroidBot
/**
* 监管 child [Job]s

View File

@ -0,0 +1,131 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum
import net.mamoe.mirai.internal.utils.ScheduledJob
import net.mamoe.mirai.internal.utils.groupCacheDir
import net.mamoe.mirai.utils.createFileIfNotExists
import net.mamoe.mirai.utils.info
import net.mamoe.mirai.utils.runBIO
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.time.milliseconds
internal val JsonForCache = Json {
encodeDefaults = true
ignoreUnknownKeys = true
isLenient = true
prettyPrint = true
}
internal val ProtoBufForCache = ProtoBuf {
encodeDefaults = true
}
@Serializable
internal data class FriendListCache(
var friendListSeq: Long = 0,
/**
* 实际上是个序列号, 不是时间
*/
var timeStamp: Long = 0,
var list: List<FriendInfoImpl> = emptyList(),
)
@Serializable
internal data class GroupMemberListCache(
var troopMemberNumSeq: Long,
var list: List<MemberInfoImpl> = emptyList(),
)
internal fun GroupMemberListCache.isValid(stTroopNum: StTroopNum): Boolean {
return this.list.size == stTroopNum.dwMemberNum?.toInt() && this.troopMemberNumSeq == stTroopNum.dwMemberNumSeq
}
internal class GroupMemberListCaches(
private val bot: QQAndroidBot,
) {
init {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.eventChannel.parentScope(bot)
.subscribeAlways<net.mamoe.mirai.event.events.BaseGroupMemberInfoChangeEvent> {
groupListSaver.notice()
}
}
private val changedGroups: MutableCollection<Long> = ConcurrentLinkedQueue()
private val groupListSaver by lazy {
ScheduledJob(bot.coroutineContext, bot.configuration.contactListCache.saveIntervalMillis.milliseconds) {
runBIO { saveGroupCaches() }
}
}
fun reportChanged(groupCode: Long) {
changedGroups.add(groupCode)
groupListSaver.notice()
}
private fun takeCurrentChangedGroups(): Map<Long, GroupMemberListCache> {
val ret = HashMap<Long, GroupMemberListCache>()
changedGroups.removeIf {
ret[it] = get(it)
true
}
return ret
}
private val cacheDir by lazy { bot.configuration.groupCacheDir() }
private fun resolveCacheFile(groupCode: Long): File {
cacheDir.mkdirs()
return cacheDir.resolve("$groupCode.json")
}
fun saveGroupCaches() {
val currentChanged = takeCurrentChangedGroups()
if (currentChanged.isNotEmpty()) {
for ((id, cache) in currentChanged) {
val file = resolveCacheFile(id)
file.createFileIfNotExists()
file.writeText(JsonForCache.encodeToString(GroupMemberListCache.serializer(), cache))
}
bot.network.logger.info { "Saved ${currentChanged.size} groups to local cache." }
}
}
val map: MutableMap<Long, GroupMemberListCache> = ConcurrentHashMap()
fun retainAll(list: Collection<Long>) {
this.map.keys.retainAll(list)
}
operator fun get(id: Long): GroupMemberListCache {
return map.getOrPut(id) {
val file = resolveCacheFile(id)
if (file.exists() && file.isFile) {
val text = file.readText()
if (text.isNotBlank()) {
return JsonForCache.decodeFromString(GroupMemberListCache.serializer(), text)
}
}
GroupMemberListCache(0, emptyList())
}
}
}

View File

@ -0,0 +1,238 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network
import contact.StrangerImpl
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.data.FriendInfo
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.FriendImpl
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
import net.mamoe.mirai.internal.contact.info.GroupInfoImpl
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
import net.mamoe.mirai.internal.contact.toMiraiFriendInfo
import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum
import net.mamoe.mirai.internal.network.protocol.data.jce.SvcRespRegister
import net.mamoe.mirai.internal.network.protocol.data.jce.isValid
import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
import net.mamoe.mirai.internal.network.protocol.packet.list.StrangerList
import net.mamoe.mirai.utils.info
import net.mamoe.mirai.utils.retryCatching
import net.mamoe.mirai.utils.verbose
internal interface ContactUpdater {
suspend fun loadAll(registerResp: SvcRespRegister)
fun closeAllContacts(e: CancellationException)
}
internal class ContactUpdaterImpl(
val bot: QQAndroidBot,
) : ContactUpdater {
@Synchronized
override suspend fun loadAll(registerResp: SvcRespRegister) {
coroutineScope {
launch { reloadFriendList(registerResp) }
launch { reloadGroupList() }
launch { reloadStrangerList() }
}
}
@Synchronized
override fun closeAllContacts(e: CancellationException) {
if (!initFriendOk) {
bot.friends.delegate.removeAll { it.cancel(e); true }
}
if (!initGroupOk) {
bot.groups.delegate.removeAll { it.cancel(e); true }
}
if (!initStrangerOk) {
bot.strangers.delegate.removeAll { it.cancel(e); true }
}
}
@Volatile
private var initFriendOk = false
@Volatile
private var initGroupOk = false
@Volatile
private var initStrangerOk = false
/**
* Don't use concurrently
*/
private suspend fun reloadFriendList(registerResp: SvcRespRegister) = bot.network.run {
if (initFriendOk) {
return
}
val friendListCache = bot.friendListCache
fun updateCacheSeq(list: List<FriendInfoImpl>) {
bot.friendListCache?.apply {
friendListSeq = registerResp.iLargeSeq
timeStamp = registerResp.timeStamp
this.list = list
bot.saveFriendCache()
}
}
suspend fun refreshFriendList(): List<FriendInfoImpl> {
logger.info { "Start loading friend list..." }
val friendInfos = mutableListOf<FriendInfoImpl>()
var count = 0
var total: Short
while (true) {
val data = FriendList.GetFriendGroupList(
bot.client, count, 150, 0, 0
).sendAndExpect<FriendList.GetFriendGroupList.Response>(timeoutMillis = 5000, retry = 2)
total = data.totalFriendCount
for (jceInfo in data.friendList) {
friendInfos.add(jceInfo.toMiraiFriendInfo())
}
count += data.friendList.size
logger.verbose { "Loading friend list: ${count}/${total}" }
if (count >= total) break
}
logger.info { "Successfully loaded friend list: $count in total" }
return friendInfos
}
val list = if (friendListCache?.isValid(registerResp) == true) {
val list = friendListCache.list
bot.network.logger.info { "Loaded ${list.size} friends from local cache." }
// For sync bot nick
FriendList.GetFriendGroupList(
bot.client, 0, 1, 0, 0
).sendAndExpect<Packet>()
list
} else {
refreshFriendList().also {
updateCacheSeq(it)
}
}
for (friendInfoImpl in list) {
addFriendToBot(friendInfoImpl)
}
initFriendOk = true
}
private fun addFriendToBot(it: FriendInfo) =
bot.friends.delegate.add(FriendImpl(bot, bot.coroutineContext, it))
private suspend fun addGroupToBot(stTroopNum: StTroopNum) = stTroopNum.run {
suspend fun refreshGroupMemberList(): Sequence<MemberInfo> {
return Mirai.getRawGroupMemberList(
bot,
groupUin,
groupCode,
dwGroupOwnerUin
)
}
val cache = bot.groupMemberListCaches?.get(groupCode)
val members = if (cache != null) {
if (cache.isValid(stTroopNum)) {
cache.list.asSequence().also {
bot.network.logger.info { "Loaded ${cache.list.size} members from local cache for group ${groupName} (${groupCode})" }
}
} else refreshGroupMemberList().also { sequence ->
cache.troopMemberNumSeq = dwMemberNumSeq ?: 0
cache.list = sequence.mapTo(ArrayList()) { it as MemberInfoImpl }
bot.groupMemberListCaches!!.reportChanged(groupCode)
}
} else {
refreshGroupMemberList()
}
bot.groups.delegate.add(
GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = groupCode,
groupInfo = GroupInfoImpl(stTroopNum),
members = members
)
)
}
private suspend fun reloadStrangerList() = bot.network.run {
if (initStrangerOk) {
return
}
var currentCount = 0
logger.info { "Start loading stranger list..." }
val response = StrangerList.GetStrangerList(bot.client)
.sendAndExpect<StrangerList.GetStrangerList.Response>(timeoutMillis = 5000, retry = 2)
if (response.result == 0) {
response.strangerList.forEach {
// atomic
bot.strangers.delegate.add(
StrangerImpl(bot, bot.coroutineContext, StrangerInfoImpl(it.uin, it.nick.decodeToString()))
).also { currentCount++ }
}
}
logger.info { "Successfully loaded stranger list: $currentCount in total" }
initStrangerOk = true
}
private suspend fun reloadGroupList() = bot.network.run {
if (initGroupOk) {
return
}
TroopManagement.GetTroopConfig(bot.client).sendAndExpect<TroopManagement.GetTroopConfig.Response>()
logger.info { "Start loading group list..." }
val troopListData = FriendList.GetTroopListSimplify(bot.client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 5)
val semaphore = Semaphore(30)
coroutineScope {
troopListData.groups.forEach { group ->
launch {
semaphore.withPermit {
retryCatching(5) { addGroupToBot(group) }.getOrThrow()
}
}
}
}
logger.info { "Successfully loaded group list: ${troopListData.groups.size} in total." }
bot.groupMemberListCaches?.saveGroupCaches()
initGroupOk = true
}
}

View File

@ -14,11 +14,11 @@ package net.mamoe.mirai.internal.network
import kotlinx.atomicfu.AtomicBoolean
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CompletableDeferred
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.String
import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully
import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.OnlineStatus
import net.mamoe.mirai.internal.BotAccount
import net.mamoe.mirai.internal.QQAndroidBot
@ -45,7 +45,7 @@ internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Ra
// [114.221.148.179:14000, 113.96.13.125:8080, 14.22.3.51:8080, 42.81.172.207:443, 114.221.144.89:80, 125.94.60.148:14000, 42.81.192.226:443, 114.221.148.233:8080, msfwifi.3g.qq.com:8080, 42.81.172.22:80]
internal val DefaultServerList: MutableSet<Pair<String, Int>> =
"114.221.148.179:14000, 113.96.13.125:8080, 14.22.3.51:8080, 42.81.172.207:443, 114.221.144.89:80, 125.94.60.148:14000, 42.81.192.226:443, 114.221.148.233:8080, msfwifi.3g.qq.com:8080, 42.81.172.22:80"
"msfwifi.3g.qq.com:8080, 14.215.138.110:8080, 113.96.12.224:8080, 157.255.13.77:14000, 120.232.18.27:443, 183.3.235.162:14000, 163.177.89.195:443, 183.232.94.44:80, 203.205.255.224:8080, 203.205.255.221:8080"
.split(", ")
.map {
val host = it.substringBefore(':')
@ -165,6 +165,8 @@ internal open class QQAndroidClient(
class MessageSvcSyncData {
val firstNotify: AtomicBoolean = atomic(true)
var latestMsgNewGroupTime: Long = currentTimeSeconds()
var latestMsgNewFriendTime: Long = currentTimeSeconds()
@Volatile
var syncCookie: ByteArray? = null
@ -180,12 +182,13 @@ internal open class QQAndroidClient(
val pbGetMessageCacheList = SyncingCacheList<PbGetMessageSyncId>()
internal data class SystemMsgNewGroupSyncId(
internal data class SystemMsgNewSyncId(
val sequence: Long,
val time: Long
)
val systemMsgNewGroupCacheList = SyncingCacheList<SystemMsgNewGroupSyncId>(10)
val systemMsgNewGroupCacheList = SyncingCacheList<SystemMsgNewSyncId>(10)
val systemMsgNewFriendCacheList = SyncingCacheList<SystemMsgNewSyncId>(10)
internal data class PbPushTransMsgSyncId(
@ -281,11 +284,6 @@ internal open class QQAndroidClient(
lateinit var t104: ByteArray
/**
* from ConfigPush.PushReq
*/
@JvmField
val bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred()
}
internal fun BytePacketBuilder.writeLoginExtraData(loginExtraData: LoginExtraData) {
@ -298,6 +296,7 @@ internal fun BytePacketBuilder.writeLoginExtraData(loginExtraData: LoginExtraDat
}
}
@Serializable
internal class BdhSession(
val sigSession: ByteArray,
val sessionKey: ByteArray,

View File

@ -0,0 +1,129 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.handler
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.BdhSession
import net.mamoe.mirai.internal.network.JsonForCache
import net.mamoe.mirai.internal.network.ProtoBufForCache
import net.mamoe.mirai.internal.utils.actualCacheDir
import java.io.File
@Serializable
private data class ServerHostAndPort(
val host: String,
val port: Int,
)
private val ServerListSerializer: KSerializer<List<ServerHostAndPort>> =
ListSerializer(ServerHostAndPort.serializer())
@OptIn(ExperimentalCoroutinesApi::class)
internal class BdhSessionSyncer(
private val bot: QQAndroidBot
) {
var bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred()
val hasSession: Boolean
get() = kotlin.runCatching { bdhSession.getCompleted() }.isSuccess
fun overrideSession(
session: BdhSession,
doSave: Boolean = true
) {
bdhSession.complete(session)
bdhSession = CompletableDeferred(session)
if (doSave) {
saveToCache()
}
}
private val sessionCacheFile: File
get() = bot.configuration.actualCacheDir().resolve("session.bin")
private val serverListCacheFile: File
get() = bot.configuration.actualCacheDir().resolve("servers.json")
fun loadServerListFromCache() {
val serverListCacheFile = this.serverListCacheFile
if (serverListCacheFile.isFile) {
bot.network.logger.verbose("Loading server list from cache.")
kotlin.runCatching {
val list = JsonForCache.decodeFromString(ServerListSerializer, serverListCacheFile.readText())
bot.serverList.clear()
bot.serverList.addAll(list.map { it.host to it.port })
}.onFailure {
bot.network.logger.warning("Error in loading server list from cache", it)
}
} else {
bot.network.logger.verbose("No server list cached.")
}
}
fun loadFromCache() {
val sessionCacheFile = this.sessionCacheFile
if (sessionCacheFile.isFile) {
bot.network.logger.verbose("Loading BdhSession from cache file")
kotlin.runCatching {
overrideSession(
ProtoBufForCache.decodeFromByteArray(BdhSession.serializer(), sessionCacheFile.readBytes()),
doSave = false
)
}.onFailure {
kotlin.runCatching { sessionCacheFile.delete() }
bot.network.logger.warning("Error in loading BdhSession from cache", it)
}
} else {
bot.network.logger.verbose("No BdhSession cache")
}
}
fun saveServerListToCache() {
val serverListCacheFile = this.serverListCacheFile
serverListCacheFile.parentFile?.mkdirs()
bot.network.logger.verbose("Saving server list to cache")
kotlin.runCatching {
serverListCacheFile.writeText(
JsonForCache.encodeToString(
ServerListSerializer,
bot.serverList.map { ServerHostAndPort(it.first, it.second) }
)
)
}.onFailure {
bot.network.logger.warning("Error in saving ServerList to cache.", it)
}
}
fun saveToCache() {
val sessionCacheFile = this.sessionCacheFile
sessionCacheFile.parentFile?.mkdirs()
if (bdhSession.isCompleted) {
bot.network.logger.verbose("Saving bdh session to cache")
kotlin.runCatching {
sessionCacheFile.writeBytes(
ProtoBufForCache.encodeToByteArray(
BdhSession.serializer(),
bdhSession.getCompleted()
)
)
}.onFailure {
bot.network.logger.warning("Error in saving BdhSession to cache.", it)
}
} else {
sessionCacheFile.delete()
bot.network.logger.verbose("No BdhSession to save to cache")
}
}
}

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