mirror of
https://github.com/mamoe/mirai.git
synced 2024-12-28 09:30:10 +08:00
Merge branch 'dev' into android_target
# Conflicts: # build.gradle.kts # buildSrc/src/main/kotlin/Versions.kt
This commit is contained in:
commit
0b971b2117
123
.github/workflows/bintray.yml
vendored
123
.github/workflows/bintray.yml
vendored
@ -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 }}
|
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@ -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
117
.github/workflows/release.yml
vendored
Normal 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
7
.gitignore
vendored
@ -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/
|
||||
|
@ -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>
|
@ -85,6 +85,7 @@ mirai 是一个在全平台下运行,提供 QQ Android 协议支持的高效
|
||||
- 闪照
|
||||
- 撤回群员消息
|
||||
- 自定义消息
|
||||
- 音乐分享
|
||||
|
||||
**群相关**
|
||||
- 群列表
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
108
buildSrc/src/main/kotlin/GpgSigner.kt
Normal file
108
buildSrc/src/main/kotlin/GpgSigner.kt
Normal 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())
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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}"
|
||||
}
|
||||
|
102
buildSrc/src/main/kotlin/PublishingGpgSign.kt
Normal file
102
buildSrc/src/main/kotlin/PublishingGpgSign.kt
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}"
|
123
buildSrc/src/main/kotlin/keys/SecretKeys.kt
Normal file
123
buildSrc/src/main/kotlin/keys/SecretKeys.kt
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
22
ci-release-helper/build.gradle.kts
Normal file
22
ci-release-helper/build.gradle.kts
Normal 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
|
||||
}
|
55
docs/Bots.md
55
docs/Bots.md
@ -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 客户端登录后修改密码 |
|
||||
|
||||
若以上方案无法解决问题,请尝试 [切换登录协议](#切换登录协议) 和 **[处理滑动验证码](#处理滑动验证码)**。
|
||||
|
||||
|
@ -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>
|
||||
```
|
||||
|
@ -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` 实例
|
||||
|
||||
|
@ -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`]:
|
||||
|
@ -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.bat
vendored
Normal file → Executable file
0
gradlew.bat
vendored
Normal file → Executable file
@ -1 +1 @@
|
||||
Subproject commit 3f98d8ec2abfa963c5f720f32b7b27e863569bc8
|
||||
Subproject commit 086604ce67792ae835b3cf34e1b41edf11d0e7d6
|
@ -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`)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.Bot
|
||||
import kotlin.internal.InlineOnly
|
||||
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
* 好友输入状态改变的事件,当开始输入文字、退出聊天窗口或清空输入框时会触发此事件
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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] 并覆盖.
|
||||
|
@ -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 ->
|
||||
|
@ -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<*>
|
||||
}
|
67
mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt
Normal file
67
mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt
Normal 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))
|
||||
}
|
||||
}
|
@ -15,7 +15,9 @@ import net.mamoe.mirai.utils.safeCast
|
||||
/**
|
||||
* 商城表情
|
||||
*
|
||||
* 目前不支持直接发送,可保存接收到的来自官方客户端的商城表情然后转发.
|
||||
* 除 [Dice] 可以发送外, 目前不支持直接发送,可保存接收到的来自官方客户端的商城表情然后转发.
|
||||
*
|
||||
* @see Dice
|
||||
*/
|
||||
public interface MarketFace : HummerMessage {
|
||||
/**
|
||||
|
@ -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] 的捷径
|
||||
*/
|
||||
|
@ -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()
|
||||
|
||||
/**
|
||||
* 在一段时间后撤回这条消息.
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
@ -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() })
|
||||
}
|
||||
|
||||
|
@ -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 = "[语音消息]"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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() }
|
@ -7,6 +7,10 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("MiraiUtils")
|
||||
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
|
||||
|
28
mirai-core-utils/src/commonMain/kotlin/Serialization.kt
Normal file
28
mirai-core-utils/src/commonMain/kotlin/Serialization.kt
Normal 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())
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
@ -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
|
@ -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,
|
||||
)
|
||||
}
|
@ -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,
|
@ -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
|
@ -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?
|
||||
}
|
93
mirai-core/src/commonMain/kotlin/message/MarketFaceImpl.kt
Normal file
93
mirai-core/src/commonMain/kotlin/message/MarketFaceImpl.kt
Normal 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
|
||||
}
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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]"
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
/*
|
||||
|
292
mirai-core/src/commonMain/kotlin/message/messageToElems.kt
Normal file
292
mirai-core/src/commonMain/kotlin/message/messageToElems.kt
Normal 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()
|
@ -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
|
||||
|
@ -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
|
||||
|
131
mirai-core/src/commonMain/kotlin/network/ContactListCache.kt
Normal file
131
mirai-core/src/commonMain/kotlin/network/ContactListCache.kt
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
238
mirai-core/src/commonMain/kotlin/network/ContactUpdater.kt
Normal file
238
mirai-core/src/commonMain/kotlin/network/ContactUpdater.kt
Normal 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
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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,
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user